996 lines
36 KiB
Python
996 lines
36 KiB
Python
|
import sqlite3
|
||
|
from datetime import datetime, timedelta, time
|
||
|
from fastapi import FastAPI, HTTPException, Depends, WebSocket, WebSocketDisconnect, Header
|
||
|
from fastapi.middleware.cors import CORSMiddleware
|
||
|
from pydantic import BaseModel
|
||
|
from fastapi.responses import FileResponse
|
||
|
from typing import List, Dict, Optional
|
||
|
import uuid
|
||
|
import os
|
||
|
import json
|
||
|
import asyncio
|
||
|
from starlette.websockets import WebSocketState
|
||
|
from fpdf import FPDF
|
||
|
import tempfile
|
||
|
|
||
|
app = FastAPI()
|
||
|
|
||
|
app.add_middleware(
|
||
|
CORSMiddleware,
|
||
|
allow_origins=["*"],
|
||
|
allow_credentials=True,
|
||
|
allow_methods=["*"],
|
||
|
allow_headers=["*"],
|
||
|
)
|
||
|
|
||
|
# SQLite database setup
|
||
|
DATABASE_NAME = "school.db"
|
||
|
|
||
|
SUBJECTS = ["De", "Ma", "NW", "En", "Gs", "Re", "WN", "Fr", "Sp", "Sn"]
|
||
|
|
||
|
ROOM_NAMES = [
|
||
|
{"number": "1", "room_number": "0201", "info": "(Hörsaal)", "location": "Osttrakt"},
|
||
|
{"number": "2", "room_number": "0202", "info": "(NW)", "location": "Osttrakt"},
|
||
|
{"number": "3", "room_number": "0203", "info": "(NW)", "location": "Osttrakt"},
|
||
|
{"number": "4", "room_number": "0204", "info": "(NW)", "location": "Osttrakt"},
|
||
|
{"number": "5", "room_number": "0205", "info": "(NW)", "location": "Osttrakt"},
|
||
|
{"number": "6", "room_number": "0214", "info": "(NW)", "location": "Lichthof"},
|
||
|
{"number": "7", "room_number": "0215", "info": "(NW)", "location": "Lichthof"},
|
||
|
{"number": "8", "room_number": "0216", "info": "(NW)", "location": "Flur zum Lichthof"},
|
||
|
{"number": "9", "room_number": "0217", "info": "(NW)", "location": "Flur zum Lichthof"},
|
||
|
{"number": "10", "room_number": "0265", "info": "(NW)", "location": "Lichthof"},
|
||
|
{"number": "11", "room_number": "0266", "info": "(NW)", "location": "Flur zum Lichthof"},
|
||
|
{"number": "12", "room_number": "0267", "info": "(NW)", "location": "Flur zum Lichthof"},
|
||
|
{"number": "13", "room_number": "1860", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "14", "room_number": "1861", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "15", "room_number": "1862", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "16", "room_number": "1863", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "17", "room_number": "1864", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "18", "room_number": "1865", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "19", "room_number": "1866", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "20", "room_number": "1867", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "21", "room_number": "1868", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "22", "room_number": "1869", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "23", "room_number": "1870", "info": "Jahrgang Südwest", "location": "1. OG"},
|
||
|
{"number": "24", "room_number": "2860", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "25", "room_number": "2861", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "26", "room_number": "2862", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "27", "room_number": "2863", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "28", "room_number": "2864", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "29", "room_number": "2865", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "30", "room_number": "2866", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "31", "room_number": "2867", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "32", "room_number": "2868", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "33", "room_number": "2869", "info": "Jahrgang Südwest", "location": "2. OG"},
|
||
|
{"number": "34", "room_number": "2870", "info": "Jahrgang Südwest", "location": "2. OG"}
|
||
|
]
|
||
|
room_name_dict = {room['number']: room for room in ROOM_NAMES}
|
||
|
|
||
|
active_connections: Dict[str, WebSocket] = {}
|
||
|
|
||
|
#CURRENT_TIME_HOUR_MINUTE = datetime.now().strftime("%H:%M")
|
||
|
CURRENT_DATE = datetime.now().strftime("%Y-%m-%d")
|
||
|
|
||
|
CURRENT_TIME_HOUR_MINUTE = "9:10"
|
||
|
# Helper function to connect to the database
|
||
|
def get_db_connection():
|
||
|
conn = sqlite3.connect(DATABASE_NAME)
|
||
|
conn.row_factory = sqlite3.Row
|
||
|
return conn
|
||
|
|
||
|
# Database tables creation on startup
|
||
|
def create_tables():
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
cursor.execute('''
|
||
|
CREATE TABLE IF NOT EXISTS rooms (
|
||
|
room_number TEXT NOT NULL,
|
||
|
max_students INTEGER NOT NULL,
|
||
|
teacher_name TEXT NOT NULL,
|
||
|
is_open BOOLEAN NOT NULL,
|
||
|
lesson_date TEXT NOT NULL, -- Can also be DATETIME if you need both date and time
|
||
|
lesson_time TEXT NOT NULL,
|
||
|
unique_id TEXT PRIMARY KEY, -- Ensure unique_id is the primary key
|
||
|
UNIQUE(room_number, lesson_date, lesson_time) -- Composite unique constraint
|
||
|
)
|
||
|
''')
|
||
|
cursor.execute('''
|
||
|
CREATE TABLE IF NOT EXISTS student_joins (
|
||
|
student_username TEXT NOT NULL,
|
||
|
room_unique_id TEXT NOT NULL,
|
||
|
join_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
PRIMARY KEY (student_username, room_unique_id),
|
||
|
FOREIGN KEY (student_username) REFERENCES students(username) ON DELETE CASCADE,
|
||
|
FOREIGN KEY (room_unique_id) REFERENCES rooms(unique_id) ON DELETE CASCADE
|
||
|
)
|
||
|
''')
|
||
|
|
||
|
cursor.execute('''
|
||
|
CREATE TABLE IF NOT EXISTS students (
|
||
|
username TEXT PRIMARY KEY NOT NULL,
|
||
|
password TEXT NOT NULL,
|
||
|
first_name TEXT,
|
||
|
last_name TEXT,
|
||
|
user_class TEXT,
|
||
|
unique_id TEXT
|
||
|
)
|
||
|
''')
|
||
|
|
||
|
|
||
|
cursor.execute('''
|
||
|
CREATE TABLE IF NOT EXISTS teachers (
|
||
|
username TEXT PRIMARY KEY,
|
||
|
password TEXT,
|
||
|
first_name TEXT,
|
||
|
last_name TEXT,
|
||
|
subjects TEXT
|
||
|
)
|
||
|
''')
|
||
|
|
||
|
|
||
|
cursor.execute('''
|
||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||
|
session_id TEXT PRIMARY KEY,
|
||
|
username TEXT,
|
||
|
role TEXT
|
||
|
)
|
||
|
''')
|
||
|
|
||
|
# Clear all rooms and sessions on startup
|
||
|
cursor.execute("DELETE FROM rooms")
|
||
|
cursor.execute("DELETE FROM sessions")
|
||
|
cursor.execute("DELETE FROM student_joins")
|
||
|
conn.commit()
|
||
|
conn.close()
|
||
|
|
||
|
# Models
|
||
|
class RoomCreate(BaseModel):
|
||
|
room_number: str
|
||
|
max_students: int
|
||
|
session_id: str
|
||
|
lesson_time: str
|
||
|
lesson_date: str
|
||
|
|
||
|
class Student(BaseModel):
|
||
|
username: str
|
||
|
|
||
|
class RoomInfo(BaseModel):
|
||
|
room_number: str
|
||
|
max_students: int
|
||
|
current_students: List[str]
|
||
|
teacher_name: str
|
||
|
class User_register(BaseModel):
|
||
|
username: str
|
||
|
first_name: str
|
||
|
last_name: str
|
||
|
user_class: str
|
||
|
password: str
|
||
|
|
||
|
class User(BaseModel):
|
||
|
username: str
|
||
|
password: str
|
||
|
|
||
|
class Session(BaseModel):
|
||
|
session_id: str
|
||
|
|
||
|
class Teacher_User(BaseModel):
|
||
|
username: str
|
||
|
password: str
|
||
|
teacher_secret_password: str
|
||
|
first_name: str
|
||
|
last_name: str
|
||
|
subjects: str
|
||
|
|
||
|
|
||
|
|
||
|
def current_role(session_id: str):
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if the session ID exists in the sessions table
|
||
|
cursor.execute("SELECT role FROM sessions WHERE session_id = ?", (session_id,))
|
||
|
role: list[sqlite3.Row] = cursor.fetchone()
|
||
|
|
||
|
conn.close()
|
||
|
return role
|
||
|
# Helper functions
|
||
|
def get_current_user(session_id: str):
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
cursor.execute("SELECT * FROM sessions WHERE session_id = ?", (session_id,))
|
||
|
session = cursor.fetchone()
|
||
|
|
||
|
if session is None:
|
||
|
raise HTTPException(status_code=404, detail="Session not found")
|
||
|
|
||
|
conn.close()
|
||
|
return session
|
||
|
|
||
|
def subjects_avaible(liste, worte):
|
||
|
for fach in worte:
|
||
|
if fach not in liste:
|
||
|
return False, fach # Gibt False und das nicht gefundene Fach zurück
|
||
|
return True, None
|
||
|
|
||
|
async def check_lesson_time():
|
||
|
print(f"Checking for the event at {datetime.now()}...")
|
||
|
try:
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
cursor.execute(
|
||
|
"""
|
||
|
UPDATE rooms
|
||
|
SET is_open = ?
|
||
|
WHERE lesson_date = ? AND lesson_time = ?
|
||
|
""",
|
||
|
(False, CURRENT_DATE, CURRENT_TIME_HOUR_MINUTE)
|
||
|
)
|
||
|
conn.commit()
|
||
|
conn.close()
|
||
|
event_happened = False # Replace with your actual condition
|
||
|
if event_happened:
|
||
|
print("Event occurred!")
|
||
|
return True
|
||
|
return False
|
||
|
except:
|
||
|
print("no rooms")
|
||
|
|
||
|
# Calculate seconds until the next target time
|
||
|
def seconds_until_next_interval():
|
||
|
now = datetime.now()
|
||
|
next_minute = (now.minute // 5 + 1) * 5 # Next multiple of 5 minutes
|
||
|
if next_minute >= 60: # Handle hour overflow
|
||
|
next_hour = now.hour + 1 if now.hour < 23 else 0
|
||
|
next_time = datetime(now.year, now.month, now.day, next_hour, 0, 0)
|
||
|
else:
|
||
|
next_time = datetime(now.year, now.month, now.day, now.hour, next_minute, 0)
|
||
|
return (next_time - now).total_seconds()
|
||
|
|
||
|
# Background task to check the event based on real-world time
|
||
|
async def periodic_event_checker():
|
||
|
while True:
|
||
|
try:
|
||
|
event_detected = await check_lesson_time()
|
||
|
if event_detected:
|
||
|
print("Event detected! Taking action.")
|
||
|
# Add any action you want to take when the event occurs
|
||
|
# You can also exit the loop if needed
|
||
|
break
|
||
|
sleep_time = seconds_until_next_interval()
|
||
|
print(f"Sleeping for {sleep_time:.2f} seconds until the next check.")
|
||
|
await asyncio.sleep(sleep_time)
|
||
|
except Exception as e:
|
||
|
print(f"Error in background task: {e}")
|
||
|
await asyncio.sleep(5 * 60) # Retry after 5 minutes in case of an error
|
||
|
|
||
|
|
||
|
import logging
|
||
|
|
||
|
logging.basicConfig(level=logging.DEBUG)
|
||
|
|
||
|
def is_open_update(unique_id: str):
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check the current number of students in the room
|
||
|
cursor.execute("""
|
||
|
SELECT COUNT(s.username) AS current_students,
|
||
|
COALESCE(r.max_students, 100) AS max_students,
|
||
|
COALESCE(r.is_open, 0) AS is_open
|
||
|
FROM students s
|
||
|
JOIN student_joins sj ON s.username = sj.student_username
|
||
|
JOIN rooms r ON r.unique_id = sj.room_unique_id
|
||
|
WHERE sj.room_unique_id = ?
|
||
|
""", (unique_id,))
|
||
|
|
||
|
result = cursor.fetchone()
|
||
|
if result is None:
|
||
|
logging.debug(f"No room found with unique_id: {unique_id}")
|
||
|
conn.close()
|
||
|
return
|
||
|
|
||
|
current_students = int(result['current_students'])
|
||
|
max_students = int(result['max_students'])
|
||
|
current_is_open = int(result['is_open'])
|
||
|
|
||
|
logging.debug(f"current_students: {current_students}, max_students: {max_students}, current_is_open: {current_is_open}")
|
||
|
|
||
|
# If the number of students exceeds or equals the max students, close the room
|
||
|
if current_students >= max_students:
|
||
|
if current_is_open != 0: # Check if the room is not already closed
|
||
|
cursor.execute("""
|
||
|
UPDATE rooms
|
||
|
SET is_open = 0
|
||
|
WHERE unique_id = ?
|
||
|
""", (unique_id,))
|
||
|
conn.commit()
|
||
|
else:
|
||
|
if current_is_open != 1: # Check if the room is not already
|
||
|
cursor.execute("""
|
||
|
UPDATE rooms
|
||
|
SET is_open = 1
|
||
|
WHERE unique_id = ?
|
||
|
""", (unique_id,))
|
||
|
conn.commit()
|
||
|
|
||
|
conn.close()
|
||
|
|
||
|
# Clear all data when the application starts
|
||
|
@app.on_event("startup")
|
||
|
def startup_event():
|
||
|
create_tables()
|
||
|
asyncio.create_task(periodic_event_checker())
|
||
|
|
||
|
@app.post("/teacher/register")
|
||
|
def teacher_register(user: Teacher_User):
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if username exists in either teachers or students
|
||
|
cursor.execute("SELECT * FROM teachers WHERE username = ?", (user.username,))
|
||
|
if cursor.fetchone():
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Username already exists as a teacher")
|
||
|
|
||
|
cursor.execute("SELECT * FROM students WHERE username = ?", (user.username,))
|
||
|
if cursor.fetchone():
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Username already exists as a student")
|
||
|
|
||
|
if user.teacher_secret_password != "chrissi":
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=403, detail="Incorrect teacher secret password")
|
||
|
|
||
|
|
||
|
teacher_subjects = [fach.strip() for fach in user.subjects.split(",")]
|
||
|
|
||
|
result, error_subject = subjects_avaible(SUBJECTS, teacher_subjects)
|
||
|
if result == False:
|
||
|
return{"message": "Unavaible Subject", "subject": error_subject}
|
||
|
|
||
|
cursor.execute("INSERT INTO teachers (username, password, first_name, last_name, subjects) VALUES (?, ?, ?, ?, ?)", (user.username, user.password, user.first_name, user.last_name, user.subjects))
|
||
|
conn.commit()
|
||
|
|
||
|
# Create session for teacher
|
||
|
session_id = str(uuid.uuid4())
|
||
|
cursor.execute("INSERT INTO sessions (session_id, username, role) VALUES (?, ?, ?)", (session_id, user.username, "teacher"))
|
||
|
conn.commit()
|
||
|
|
||
|
conn.close()
|
||
|
return {"message": "Teacher registered successfully", "session_id": session_id, "username": user.username}
|
||
|
|
||
|
@app.post("/student/register")
|
||
|
def student_register(user: User_register):
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if username exists in either students or teachers
|
||
|
cursor.execute("SELECT * FROM students WHERE username = ?", (user.username,))
|
||
|
if cursor.fetchone():
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Benutzername bereits vergeben")
|
||
|
|
||
|
cursor.execute("SELECT * FROM teachers WHERE username = ?", (user.username,))
|
||
|
if cursor.fetchone():
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Benutzername bereits vergeben")
|
||
|
|
||
|
cursor.execute("INSERT INTO students (username, password, first_name, last_name, user_class) VALUES (?, ?, ?, ?, ?)", (user.username, user.password, user.first_name, user.last_name, user.user_class))
|
||
|
conn.commit()
|
||
|
|
||
|
# Create session for student
|
||
|
session_id = str(uuid.uuid4())
|
||
|
cursor.execute("INSERT INTO sessions (session_id, username, role) VALUES (?, ?, ?)", (session_id, user.username, "student"))
|
||
|
conn.commit()
|
||
|
|
||
|
conn.close()
|
||
|
return {"message": "Schüler erfolgreich registriert", "session_id": session_id, "username": user.username}
|
||
|
|
||
|
@app.post("/login")
|
||
|
def login(user: User):
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
cursor.execute("SELECT * FROM teachers WHERE username = ?", (user.username,))
|
||
|
teacher = cursor.fetchone()
|
||
|
if teacher:
|
||
|
if teacher["password"] != user.password:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=403, detail="Anmeldung Fehlgeschlagen! Falscher Benutzername oder falsches Passwort")
|
||
|
role = "teacher"
|
||
|
else:
|
||
|
cursor.execute("SELECT * FROM students WHERE username = ?", (user.username,))
|
||
|
student = cursor.fetchone()
|
||
|
if student:
|
||
|
if student["password"] != user.password:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=403, detail="Anmeldung Fehlgeschlagen! Falscher Benutzername oder falsches Passwort")
|
||
|
role = "student"
|
||
|
else:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=404, detail="Anmeldung Fehlgeschlagen! Falscher Benutzername oder falsches Passwort")
|
||
|
|
||
|
session_id = str(uuid.uuid4())
|
||
|
cursor.execute("INSERT INTO sessions (session_id, username, role) VALUES (?, ?, ?)", (session_id, user.username, role))
|
||
|
conn.commit()
|
||
|
|
||
|
conn.close()
|
||
|
return {"session_id": session_id, "username": user.username}
|
||
|
|
||
|
@app.post("/teacher/create_room")
|
||
|
def create_room(room: RoomCreate):
|
||
|
lesson_date = datetime.strptime(room.lesson_date, "%Y-%m-%d")
|
||
|
today = datetime.today().date()
|
||
|
|
||
|
if lesson_date.date() < today:
|
||
|
raise HTTPException(status_code=400, detail="Lesson date cannot be in the past")
|
||
|
|
||
|
# Check if the lesson date is today, but the lesson time has already passed
|
||
|
if lesson_date.date() == today:
|
||
|
lesson_time = datetime.strptime(room.lesson_time, "%H:%M").time()
|
||
|
current_time = datetime.now().time()
|
||
|
|
||
|
#if lesson_time < current_time:
|
||
|
# raise HTTPException(status_code=400, detail="Lesson time has already passed today")
|
||
|
|
||
|
user = get_current_user(room.session_id)
|
||
|
if user['role'] != "teacher":
|
||
|
raise HTTPException(status_code=403, detail="Only teachers can create rooms")
|
||
|
|
||
|
unique_id = str(uuid.uuid4())
|
||
|
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
cursor.execute("SELECT * FROM rooms WHERE room_number = ? AND lesson_date = ? AND lesson_time = ?", (room.room_number, room.lesson_date, room.lesson_time,))
|
||
|
if cursor.fetchone():
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Room already exists")
|
||
|
cursor.execute(
|
||
|
"INSERT INTO rooms (room_number, max_students, teacher_name, is_open, lesson_date, lesson_time, unique_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||
|
(
|
||
|
room.room_number,
|
||
|
room.max_students,
|
||
|
user['username'],
|
||
|
True,
|
||
|
room.lesson_date,
|
||
|
room.lesson_time,
|
||
|
unique_id,
|
||
|
)
|
||
|
)
|
||
|
|
||
|
conn.commit()
|
||
|
conn.close()
|
||
|
return {"message": "Room created successfully", "unique_id": unique_id}
|
||
|
|
||
|
@app.delete("/teacher/delete_room/")
|
||
|
def delete_room(unique_id: str, session: Session):
|
||
|
user = get_current_user(session.session_id)
|
||
|
if user['role'] != "teacher":
|
||
|
raise HTTPException(status_code=403, detail="Only teachers can delete rooms")
|
||
|
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
cursor.execute("SELECT * FROM rooms WHERE unique_id = ?", (unique_id,))
|
||
|
room = cursor.fetchone()
|
||
|
if room is None:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=404, detail="Room not found")
|
||
|
|
||
|
if room["teacher_name"] != user['username']:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=403, detail="You do not have permission to delete this room")
|
||
|
|
||
|
cursor.execute("DELETE FROM rooms WHERE unique_id = ?", (unique_id,))
|
||
|
conn.commit()
|
||
|
|
||
|
conn.close()
|
||
|
return {"message": f"Room {unique_id} deleted successfully by {user['username']}"}
|
||
|
|
||
|
@app.post("/teacher/room_students/")
|
||
|
def get_room_students(unique_id: str, session: Session):
|
||
|
user = get_current_user(session.session_id)
|
||
|
|
||
|
if user['role'] != "teacher":
|
||
|
raise HTTPException(status_code=403, detail="Only teachers can view students in their room")
|
||
|
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if the room exists and if it's owned by the current teacher
|
||
|
cursor.execute("SELECT * FROM rooms WHERE unique_id = ? AND teacher_name = ?", (unique_id, user['username']))
|
||
|
room = cursor.fetchone()
|
||
|
|
||
|
if room is None:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=404, detail="Room not found or not owned by this teacher")
|
||
|
|
||
|
# Fetch the students who are currently joined to the room
|
||
|
cursor.execute("""
|
||
|
SELECT s.username
|
||
|
FROM students s
|
||
|
JOIN student_joins sj ON s.username = sj.student_username
|
||
|
WHERE sj.room_unique_id = ?
|
||
|
""", (unique_id,))
|
||
|
|
||
|
students = [row['username'] for row in cursor.fetchall()]
|
||
|
|
||
|
conn.close()
|
||
|
return {"unique_id": unique_id, "students": students}
|
||
|
|
||
|
@app.websocket("/ws/student/my_room")
|
||
|
async def websocket_get_my_room(websocket: WebSocket, session_id: Optional[str] = Header(None)):
|
||
|
await websocket.accept()
|
||
|
# Validate session ID and get user
|
||
|
user = get_current_user(session_id)
|
||
|
|
||
|
if not user or user['role'] != "student":
|
||
|
await websocket.close(code=403)
|
||
|
return
|
||
|
|
||
|
# Store active connection
|
||
|
active_connections[user['username']] = websocket
|
||
|
|
||
|
try:
|
||
|
while True:
|
||
|
# Simulate periodic room and teacher subjects fetch
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Fetch rooms the student has joined
|
||
|
cursor.execute(
|
||
|
"""
|
||
|
SELECT r.unique_id, r.room_number, r.teacher_name, r.max_students, r.lesson_date, r.lesson_time,
|
||
|
t.first_name, t.last_name
|
||
|
FROM student_joins sj
|
||
|
JOIN rooms r ON sj.room_unique_id = r.unique_id
|
||
|
JOIN teachers t ON r.teacher_name = t.username
|
||
|
WHERE sj.student_username = ?
|
||
|
""",
|
||
|
(user['username'],)
|
||
|
)
|
||
|
|
||
|
rooms = cursor.fetchall()
|
||
|
|
||
|
# Fetch teacher subjects
|
||
|
cursor.execute("SELECT username, subjects FROM teachers")
|
||
|
teacher_subjects_raw = cursor.fetchall()
|
||
|
teacher_subjects = {row[0]: row[1] for row in teacher_subjects_raw}
|
||
|
|
||
|
rooms_list = []
|
||
|
for room in rooms:
|
||
|
room_dict = dict(room)
|
||
|
room_number = room_dict.get("room_number")
|
||
|
teacher_name = room_dict.get("teacher_name")
|
||
|
|
||
|
# Add additional room details from room_name_dict
|
||
|
if room_number and room_number in room_name_dict:
|
||
|
room_dict.update(room_name_dict[room_number])
|
||
|
|
||
|
# Add teacher subjects if available
|
||
|
if teacher_name and teacher_name in teacher_subjects:
|
||
|
room_dict["subjects"] = teacher_subjects[teacher_name]
|
||
|
|
||
|
# Fetch current student count for the room
|
||
|
cursor.execute(
|
||
|
"""
|
||
|
SELECT COUNT(s.username) as current_students
|
||
|
FROM students s
|
||
|
JOIN student_joins sj ON s.username = sj.student_username
|
||
|
WHERE sj.room_unique_id = ?
|
||
|
""",
|
||
|
(room_dict["unique_id"],)
|
||
|
)
|
||
|
current_students = cursor.fetchone()["current_students"]
|
||
|
room_dict["current_students"] = current_students
|
||
|
|
||
|
rooms_list.append(room_dict)
|
||
|
|
||
|
conn.close()
|
||
|
|
||
|
if rooms_list:
|
||
|
await websocket.send_json({"rooms": rooms_list})
|
||
|
else:
|
||
|
await websocket.send_json({"error": "Student has not joined any rooms"})
|
||
|
|
||
|
await asyncio.sleep(2) # Poll every 2 seconds
|
||
|
except WebSocketDisconnect:
|
||
|
del active_connections[user['username']]
|
||
|
|
||
|
|
||
|
@app.websocket("/ws/open_rooms")
|
||
|
async def websocket_open_rooms(websocket: WebSocket, session_id: Optional[str] = Header(None)):
|
||
|
await websocket.accept()
|
||
|
|
||
|
# Validate session ID and get user
|
||
|
user = get_current_user(session_id)
|
||
|
|
||
|
if not user or user['role'] != "student":
|
||
|
await websocket.close(code=403)
|
||
|
return
|
||
|
|
||
|
# Store active connection
|
||
|
active_connections[user['username']] = websocket
|
||
|
|
||
|
try:
|
||
|
while True:
|
||
|
# Simulate periodic room fetch
|
||
|
conn = sqlite3.connect('school.db')
|
||
|
conn.row_factory = sqlite3.Row
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Fetch all rooms, both open and closed
|
||
|
cursor.execute("""
|
||
|
SELECT r.unique_id, r.room_number, r.teacher_name, r.max_students,
|
||
|
COUNT(s.username) AS current_students,
|
||
|
r.is_open,
|
||
|
r.lesson_date, r.lesson_time,
|
||
|
t.first_name, t.last_name, t.subjects,
|
||
|
CASE
|
||
|
WHEN EXISTS (
|
||
|
SELECT 1
|
||
|
FROM student_joins sj
|
||
|
JOIN sessions ss ON sj.student_username = ss.username
|
||
|
WHERE sj.room_unique_id = r.unique_id AND ss.session_id = ?
|
||
|
) THEN 1
|
||
|
ELSE 0
|
||
|
END AS joined
|
||
|
FROM rooms r
|
||
|
LEFT JOIN student_joins sj ON r.unique_id = sj.room_unique_id
|
||
|
LEFT JOIN students s ON sj.student_username = s.username
|
||
|
LEFT JOIN teachers t ON r.teacher_name = t.username
|
||
|
WHERE r.lesson_date = ?
|
||
|
GROUP BY r.unique_id
|
||
|
""", (session_id, CURRENT_DATE,))
|
||
|
|
||
|
all_rooms = []
|
||
|
for row in cursor.fetchall():
|
||
|
room = dict(row)
|
||
|
room_number = str(room['room_number'])
|
||
|
|
||
|
room_info = room_name_dict.get(room_number, {})
|
||
|
room.update({
|
||
|
'room_number': int(room_number),
|
||
|
'room_name': room_info.get('room_number', ''),
|
||
|
'info': room_info.get('info', ''),
|
||
|
'location': room_info.get('location', ''),
|
||
|
'joined': bool(room['joined']), # Whether the user joined
|
||
|
'max_students': room['max_students'], # Maximum capacity
|
||
|
'current_students': room['current_students'], # Current count
|
||
|
'is_open': bool(room['is_open']), # Room open status
|
||
|
})
|
||
|
|
||
|
all_rooms.append(room)
|
||
|
|
||
|
conn.close()
|
||
|
|
||
|
if all_rooms:
|
||
|
await websocket.send_json({"rooms": all_rooms})
|
||
|
else:
|
||
|
await websocket.send_json({"message": "No rooms available"})
|
||
|
await asyncio.sleep(1)
|
||
|
except HTTPException:
|
||
|
await websocket.send_json({"error": "Session not found"})
|
||
|
await websocket.close(code=404)
|
||
|
return
|
||
|
|
||
|
except WebSocketDisconnect:
|
||
|
del active_connections[user['username']]
|
||
|
|
||
|
|
||
|
|
||
|
@app.post("/student/join_room/")
|
||
|
def join_room(unique_id: str, session: Session):
|
||
|
user = get_current_user(session.session_id)
|
||
|
|
||
|
if user['role'] != "student":
|
||
|
raise HTTPException(status_code=403, detail="Nur Schüler können diesem Raum beitreten")
|
||
|
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if the room exists
|
||
|
cursor.execute("SELECT * FROM rooms WHERE unique_id = ?", (unique_id,))
|
||
|
room = cursor.fetchone()
|
||
|
if room is None:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=404, detail="Raum nicht gefunden")
|
||
|
|
||
|
# Check if the room is open
|
||
|
if not room["is_open"]:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Raum ist geschlossen")
|
||
|
|
||
|
# Check if the student is already in a room at the same time (same lesson_date and lesson_time)
|
||
|
cursor.execute("""
|
||
|
SELECT r.unique_id
|
||
|
FROM student_joins sj
|
||
|
JOIN rooms r ON sj.room_unique_id = r.unique_id
|
||
|
WHERE sj.student_username = ? AND r.lesson_date = ? AND r.lesson_time = ?
|
||
|
""", (user['username'], room["lesson_date"], room["lesson_time"]))
|
||
|
conflicting_room = cursor.fetchone()
|
||
|
|
||
|
if conflicting_room:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Du bist bereits einem Raum für die gleiche Stunde zugewiesen")
|
||
|
|
||
|
# Check the current number of students in the room
|
||
|
cursor.execute("""
|
||
|
SELECT COUNT(sj.student_username) as current_students
|
||
|
FROM student_joins sj
|
||
|
WHERE sj.room_unique_id = ?
|
||
|
""", (unique_id,))
|
||
|
current_students = cursor.fetchone()["current_students"]
|
||
|
|
||
|
if current_students >= room["max_students"]:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="Raum ist voll")
|
||
|
|
||
|
# Add the student to the room by inserting into student_joins table
|
||
|
cursor.execute("""
|
||
|
INSERT INTO student_joins (student_username, room_unique_id)
|
||
|
VALUES (?, ?)
|
||
|
""", (user['username'], unique_id))
|
||
|
|
||
|
conn.commit()
|
||
|
conn.close()
|
||
|
|
||
|
is_open_update(unique_id)
|
||
|
return {"message": f"Joined room {unique_id}"}
|
||
|
|
||
|
|
||
|
|
||
|
@app.post("/student/leave_room/")
|
||
|
def leave_room(unique_id: str, session: Session):
|
||
|
user = get_current_user(session.session_id)
|
||
|
|
||
|
if user['role'] != "student":
|
||
|
raise HTTPException(status_code=403, detail="Only students can leave rooms")
|
||
|
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if the student is in the specified room by checking the student_joins table
|
||
|
cursor.execute("SELECT * FROM student_joins WHERE student_username = ? AND room_unique_id = ?",
|
||
|
(user['username'], unique_id))
|
||
|
student_join = cursor.fetchone()
|
||
|
|
||
|
if not student_join:
|
||
|
conn.close()
|
||
|
raise HTTPException(status_code=400, detail="You are not in the specified room")
|
||
|
|
||
|
# Remove the student from the room by deleting the entry from student_joins
|
||
|
cursor.execute("DELETE FROM student_joins WHERE student_username = ? AND room_unique_id = ?",
|
||
|
(user['username'], unique_id))
|
||
|
conn.commit()
|
||
|
|
||
|
conn.close()
|
||
|
is_open_update(unique_id)
|
||
|
return {"message": f"You have left room {unique_id}"}
|
||
|
|
||
|
|
||
|
@app.post("/check_session")
|
||
|
def check_session(session: Session):
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if the session ID exists in the sessions table
|
||
|
cursor.execute("SELECT * FROM sessions WHERE session_id = ?", (session.session_id,))
|
||
|
session_data = cursor.fetchone()
|
||
|
|
||
|
conn.close()
|
||
|
|
||
|
if session_data:
|
||
|
return {"available": True}
|
||
|
else:
|
||
|
return {"available": False}
|
||
|
|
||
|
@app.post("/check_role")
|
||
|
def check_role(session: Session):
|
||
|
session_data = current_role(session.session_id)
|
||
|
|
||
|
if session_data:
|
||
|
return {"role": session_data}
|
||
|
else:
|
||
|
return {"role": None}
|
||
|
|
||
|
@app.websocket("/ws/teacher/open_rooms")
|
||
|
async def websocket_list_open_rooms_teacher(websocket: WebSocket, session_id: Optional[str] = Header(None, alias="session-id")):
|
||
|
await websocket.accept()
|
||
|
|
||
|
try:
|
||
|
# Get the current user and their role based on session_id
|
||
|
info = get_current_user(session_id)
|
||
|
# Convert info to a dictionary if it's a sqlite3.Row
|
||
|
if isinstance(info, sqlite3.Row):
|
||
|
info = dict(info) # Convert sqlite3.Row to dict
|
||
|
role = current_role(session_id)
|
||
|
teacher_username = info.get("username", "") # Access username from the info dictionary
|
||
|
except Exception as e:
|
||
|
print(f"Session validation error: {e}")
|
||
|
try:
|
||
|
await websocket.send_json({"error": "Session not found"})
|
||
|
except WebSocketDisconnect:
|
||
|
pass # Ignore if the client has disconnected
|
||
|
await websocket.close(code=4004)
|
||
|
return
|
||
|
|
||
|
role_final = ''.join([elt for elt in role])
|
||
|
# Check if the role is 'teacher'
|
||
|
if role_final != "teacher":
|
||
|
try:
|
||
|
await websocket.send_json({"message": "You are not a Teacher"})
|
||
|
except WebSocketDisconnect:
|
||
|
pass # Ignore if the client has disconnected
|
||
|
await websocket.close(code=1008)
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
# Fetch and send room data for the teacher
|
||
|
await send_room_data(websocket, teacher_username)
|
||
|
while True:
|
||
|
await asyncio.sleep(2)
|
||
|
await send_room_data(websocket, teacher_username)
|
||
|
except WebSocketDisconnect:
|
||
|
print("WebSocket disconnected")
|
||
|
except Exception as e:
|
||
|
print(f"Unexpected error: {e}")
|
||
|
try:
|
||
|
await websocket.send_json({"error": "An internal server error occurred."})
|
||
|
except WebSocketDisconnect:
|
||
|
pass # Ignore if the client has disconnected
|
||
|
await websocket.close(code=1011)
|
||
|
|
||
|
async def send_room_data(websocket: WebSocket, teacher_username: str):
|
||
|
|
||
|
try:
|
||
|
conn = get_db_connection()
|
||
|
conn.row_factory = sqlite3.Row
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Update the query to filter rooms by the teacher's username
|
||
|
cursor.execute("""
|
||
|
SELECT r.unique_id, r.room_number, r.teacher_name, r.max_students,
|
||
|
COUNT(s.username) AS current_students
|
||
|
FROM rooms r
|
||
|
LEFT JOIN student_joins sj ON r.unique_id = sj.room_unique_id
|
||
|
LEFT JOIN students s ON sj.student_username = s.username
|
||
|
WHERE r.is_open = 1 AND r.teacher_name = ?
|
||
|
GROUP BY r.unique_id
|
||
|
ORDER BY r.room_number
|
||
|
""", (teacher_username,))
|
||
|
|
||
|
open_rooms = []
|
||
|
for row in cursor.fetchall():
|
||
|
room = dict(row) # Convert the row to a dictionary
|
||
|
room_number = str(room['room_number'])
|
||
|
room_info = room_name_dict.get(room_number, {})
|
||
|
room.update({
|
||
|
'room_number': int(room_number),
|
||
|
'room_name': room_info.get('room_number', ''),
|
||
|
'info': room_info.get('info', ''),
|
||
|
'location': room_info.get('location', ''),
|
||
|
})
|
||
|
open_rooms.append(room)
|
||
|
|
||
|
conn.close()
|
||
|
|
||
|
if open_rooms:
|
||
|
try:
|
||
|
await websocket.send_json({"open_rooms": open_rooms})
|
||
|
except WebSocketDisconnect:
|
||
|
pass # Ignore if the client has disconnected
|
||
|
else:
|
||
|
try:
|
||
|
await websocket.send_json({"message": "No open rooms available"})
|
||
|
except WebSocketDisconnect:
|
||
|
pass # Ignore if the client has disconnected
|
||
|
except Exception as e:
|
||
|
print(f"Error fetching or sending room data: {e}")
|
||
|
try:
|
||
|
await websocket.send_json({"error": "Failed to fetch room data"})
|
||
|
except WebSocketDisconnect:
|
||
|
pass # Ignore if the client has disconnected
|
||
|
|
||
|
@app.get("/teacher/all_room_information")
|
||
|
def all_room_information():
|
||
|
return ROOM_NAMES
|
||
|
|
||
|
|
||
|
@app.get("/test")
|
||
|
def test():
|
||
|
conn = get_db_connection()
|
||
|
cursor = conn.cursor()
|
||
|
|
||
|
# Check if the session ID exists in the sessions table
|
||
|
cursor.execute("SELECT * FROM students WHERE username = ?", ("hi",))
|
||
|
session_data = cursor.fetchone()
|
||
|
print(session_data)
|
||
|
conn.close()
|
||
|
|
||
|
if session_data:
|
||
|
print(session_data) # Liste der Fächer ausgeben
|
||
|
return {"subjects": session_data} # Liste der Fächer zurückgeben
|
||
|
else:
|
||
|
return {"error": "Benutzer nicht gefunden oder keine Fächer vorhanden"}
|
||
|
|
||
|
@app.websocket("/ws/student/test")
|
||
|
async def websocket_get_my_room(websocket: WebSocket, session_id: Optional[str] = Header(None)):
|
||
|
await websocket.accept()
|
||
|
|
||
|
# Validate session ID
|
||
|
user = get_current_user(session_id)
|
||
|
for i in user:
|
||
|
print(i)
|
||
|
if not user:
|
||
|
await websocket.close(code=403)
|
||
|
return
|
||
|
|
||
|
# Store connection
|
||
|
active_connections[user['username']] = websocket
|
||
|
|
||
|
counter = 0 # Initialize counter to 0
|
||
|
|
||
|
try:
|
||
|
while True:
|
||
|
# Send incrementing counter value every second
|
||
|
counter += 1
|
||
|
await websocket.send_text(f"Counter: {counter}")
|
||
|
await asyncio.sleep(1) # Wait 1 second before sending the next message
|
||
|
except WebSocketDisconnect:
|
||
|
del active_connections[user['username']]
|
||
|
print(f"Connection closed for user: {user['username']}")
|
||
|
|
||
|
|
||
|
def generate_pdf(file_path):
|
||
|
class PDF(FPDF):
|
||
|
def header(self):
|
||
|
self.set_font("Arial", "B", 16)
|
||
|
today = "01.01.2025"
|
||
|
self.cell(0, 10, f"Attendance Sheet - {today}", ln=True, align="C")
|
||
|
self.ln(10)
|
||
|
|
||
|
pdf = PDF()
|
||
|
pdf.add_page()
|
||
|
pdf.set_font("Arial", size=12)
|
||
|
|
||
|
# Column headers
|
||
|
pdf.cell(80, 10, "Namen", border=1, align="C")
|
||
|
pdf.cell(50, 10, "Anwesend", border=1, align="C")
|
||
|
pdf.cell(50, 10, "Abwesend", border=1, align="C")
|
||
|
pdf.ln()
|
||
|
|
||
|
# Names list
|
||
|
names = [
|
||
|
"Jasper Grevsmühl",
|
||
|
"Papa Grevsmühl",
|
||
|
"Clara Müller",
|
||
|
"Anna Schmidt",
|
||
|
"Max Mustermann",
|
||
|
]
|
||
|
|
||
|
for name in names:
|
||
|
pdf.cell(80, 10, name, border=1)
|
||
|
pdf.cell(50, 10, "", border=1)
|
||
|
pdf.cell(50, 10, "", border=1)
|
||
|
pdf.ln()
|
||
|
|
||
|
# Save the PDF to the provided file path
|
||
|
pdf.output(file_path)
|
||
|
|
||
|
# FastAPI route for downloading the PDF
|
||
|
@app.get("/download/{filename}")
|
||
|
def download(filename: str):
|
||
|
# Prepare file path
|
||
|
temp_dir = tempfile.mkdtemp()
|
||
|
pdf_file_path = os.path.join(temp_dir, filename)
|
||
|
|
||
|
# Generate the PDF file
|
||
|
generate_pdf(pdf_file_path)
|
||
|
|
||
|
# Return the generated file as a response
|
||
|
return FileResponse(pdf_file_path)
|