Testys commited on
Commit
9d8829a
·
1 Parent(s): 96b013c

FEAT: Hopefully last change for backend to stay running

Browse files
Files changed (4) hide show
  1. .gitignore copy +0 -186
  2. main copy.py +0 -51
  3. main.py +91 -174
  4. src/models.py +1 -3
.gitignore copy DELETED
@@ -1,186 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- share/python-wheels/
24
- *.egg-info/
25
- .installed.cfg
26
- *.egg
27
- MANIFEST
28
-
29
- # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
- *.manifest
33
- *.spec
34
-
35
- # Installer logs
36
- pip-log.txt
37
- pip-delete-this-directory.txt
38
-
39
- # Unit test / coverage reports
40
- htmlcov/
41
- .tox/
42
- .nox/
43
- .coverage
44
- .coverage.*
45
- .cache
46
- nosetests.xml
47
- coverage.xml
48
- *.cover
49
- *.py,cover
50
- .hypothesis/
51
- .pytest_cache/
52
- cover/
53
-
54
- # Translations
55
- *.mo
56
- *.pot
57
-
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
- # Flask stuff:
65
- instance/
66
- .webassets-cache
67
-
68
- # Scrapy stuff:
69
- .scrapy
70
-
71
- # Sphinx documentation
72
- docs/_build/
73
-
74
- # PyBuilder
75
- .pybuilder/
76
- target/
77
-
78
- # Jupyter Notebook
79
- .ipynb_checkpoints
80
-
81
- # IPython
82
- profile_default/
83
- ipython_config.py
84
-
85
- # pyenv
86
- # For a library or package, you might want to ignore these files since the code is
87
- # intended to run in multiple environments; otherwise, check them in:
88
- # .python-version
89
-
90
- # pipenv
91
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
- # install all needed dependencies.
95
- #Pipfile.lock
96
-
97
- # UV
98
- # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- #uv.lock
102
-
103
- # poetry
104
- # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
- # This is especially recommended for binary packages to ensure reproducibility, and is more
106
- # commonly ignored for libraries.
107
- # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
- #poetry.lock
109
-
110
- # pdm
111
- # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
- #pdm.lock
113
- # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
- # in version control.
115
- # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
- .pdm.toml
117
- .pdm-python
118
- .pdm-build/
119
-
120
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
- __pypackages__/
122
-
123
- # Celery stuff
124
- celerybeat-schedule
125
- celerybeat.pid
126
-
127
- # SageMath parsed files
128
- *.sage.py
129
-
130
- # Environments
131
- .env
132
- .venv
133
- env/
134
- venv/
135
- ENV/
136
- env.bak/
137
- venv.bak/
138
-
139
- # Spyder project settings
140
- .spyderproject
141
- .spyproject
142
-
143
- # Rope project settings
144
- .ropeproject
145
-
146
- # mkdocs documentation
147
- /site
148
-
149
- # mypy
150
- .mypy_cache/
151
- .dmypy.json
152
- dmypy.json
153
-
154
- # Pyre type checker
155
- .pyre/
156
-
157
- # pytype static type analyzer
158
- .pytype/
159
-
160
- # Cython debug symbols
161
- cython_debug/
162
-
163
- # PyCharm
164
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
- # and can be added to the global gitignore or merged into this file.
167
- .idea/
168
-
169
- # Ruff stuff:
170
- .ruff_cache/
171
-
172
- # PyPI configuration file
173
- .pypirc
174
-
175
- # OS-generated files
176
- .DS_Store
177
- .DS_Store?
178
- ._*
179
- Thumbs.db
180
- ehthumbs.db
181
- desktop.ini
182
-
183
- # Editor backup/swap files
184
- *~
185
- *.swp
186
- *.swo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main copy.py DELETED
@@ -1,51 +0,0 @@
1
- from fastapi import FastAPI
2
- from fastapi.middleware.cors import CORSMiddleware # Keep if CORS is needed
3
- import uvicorn
4
- from contextlib import asynccontextmanager
5
-
6
- from src.database import create_db_tables, get_db
7
- from src.routers import students, devices, clearance, token, users, admin
8
-
9
- @asynccontextmanager
10
- async def lifespan(app_instance: FastAPI):
11
- """Handles application startup and shutdown events."""
12
- print("Application startup...")
13
- create_db_tables()
14
- print("Database tables checked/created.")
15
- yield
16
- print("Application shutdown...")
17
-
18
-
19
- # FastAPI app instance
20
- app = FastAPI(
21
- title="Undergraduate Clearance System API (ORM)",
22
- description="API for managing student clearance and RFID interactions.",
23
- version="1.1.0", # Incremented version
24
- lifespan=lifespan
25
- )
26
-
27
- app.add_middleware(
28
- CORSMiddleware,
29
- allow_origins= ['*'],
30
- allow_credentials=True,
31
- allow_methods=["*"], # Allows all methods
32
- allow_headers=["*"], # Allows all headers
33
- )
34
-
35
- # Include routers
36
- app.include_router(devices.router)
37
- app.include_router(students.router)
38
- app.include_router(clearance.router)
39
- app.include_router(token.router)
40
- app.include_router(users.router)
41
- app.include_router(admin.router)
42
-
43
- # Root endpoint
44
- @app.get("/", summary="Root endpoint", tags=["Default"])
45
- async def read_root():
46
- """Basic root endpoint to confirm the API is running."""
47
- return {"message": "Undergraduate Clearance System API is running"}
48
-
49
- # Run the FastAPI app using Uvicorn
50
- if __name__ == "__main__":
51
- uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -1,191 +1,108 @@
1
- from fastapi import FastAPI, HTTPException, Depends, status
2
- from fastapi.middleware.cors import CORSMiddleware
 
3
  import uvicorn
4
- from typing import List # Import List for type hinting
5
- from src.database import connect_db, disconnect_db, database # Also import database instance if needed directly in main
6
- from src.models import (
7
- StudentCreate, Student, ClearanceStatusCreate,
8
- ClearanceStatus, ClearanceDetail, DeviceRegister,
9
- DeviceResponse, TagScan
10
- ) # Pydantic models
11
- from src import crud # Database operations (now uses databases)
12
- from src.auth import verify_api_key # API key verification dependency (now uses databases)
 
 
 
 
 
 
13
 
14
  # FastAPI app instance
15
- app = FastAPI(title="Undergraduate Clearance System API", version="1.0.0")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- # Enable CORS (Cross-Origin Resource Sharing)
18
- # This allows your frontend (running on a different origin) to access the API
19
  app.add_middleware(
20
  CORSMiddleware,
21
- allow_origins=["*"], # WARNING: Use specific origins in production (e.g., ["http://localhost:3000", "https://your-frontend-domain.com"])
22
  allow_credentials=True,
23
- allow_methods=["*"], # Allow all HTTP methods (GET, POST, PUT, DELETE, etc.)
24
- allow_headers=["*"], # Allow all headers
25
  )
26
 
27
- # --- Database Connection Lifecycle ---
28
-
29
- # Connect to the database when the application starts up
30
- @app.on_event("startup")
31
- async def startup_event():
32
- await connect_db()
33
-
34
- # Disconnect from the database when the application shuts down
35
- @app.on_event("shutdown")
36
- async def shutdown_event():
37
- await disconnect_db()
38
-
39
-
40
- # --- Routes for ESP32 Devices ---
41
-
42
- @app.post("/api/devices/register", response_model=DeviceResponse, summary="Register or re-register an ESP32 device")
43
- async def register_device(device: DeviceRegister):
44
- """
45
- Registers a new ESP32 device or updates an existing one using 'databases'.
46
- Returns a unique API key for the device to use for subsequent requests.
47
- """
48
- # Call the crud function, which now uses 'databases'
49
- return await crud.register_device(device)
50
-
51
- @app.post("/api/scan", summary="Receive tag scan data from an ESP32 device and return clearance details")
52
- async def scan_tag(scan_data: TagScan):
53
- """
54
- Receives a tag ID from an ESP32 device, verifies the device's API key
55
- using 'databases', logs the scan event, retrieves the student's
56
- clearance details based on the tag ID, and returns the details to the device.
57
- """
58
- # Verify API key using the dependency - this will raise HTTPException if invalid
59
- # The verify_api_key function now uses 'databases'
60
- device = await verify_api_key(scan_data.api_key)
61
-
62
- # Update device last seen timestamp using the crud function (which uses databases)
63
- await crud.update_device_last_seen(scan_data.device_id)
64
-
65
- # Log the scan event using the crud function (which uses databases)
66
- await crud.create_device_log(scan_data.device_id, scan_data.tag_id, "scan")
67
-
68
- # Get student information by tag ID using the crud function (which uses databases)
69
- student = await crud.get_student_by_tag_id(scan_data.tag_id)
70
-
71
- if not student:
72
- # Return a specific message to the device if student is not found
73
- # The ESP32 firmware should handle this 404 response
74
- raise HTTPException(status_code=404, detail="Student not found for this tag")
75
-
76
- # Get clearance statuses for the student using the crud function (which uses databases)
77
- statuses = await crud.get_clearance_statuses_by_student_id(student["student_id"])
78
-
79
- # Format clearance items and determine overall status
80
- clearance_items = []
81
- overall_status = True
82
-
83
- for status_item in statuses:
84
- clearance_items.append({
85
- "department": status_item["department"],
86
- "status": status_item["status"],
87
- "remarks": status_item["remarks"],
88
- "updated_at": status_item["updated_at"].isoformat() # Format datetime to ISO string
89
- })
90
- if not status_item["status"]:
91
- overall_status = False
92
-
93
- # Return the formatted clearance details
94
- return {
95
- "student_id": student["student_id"],
96
- "name": student["name"],
97
- "department": student["department"],
98
- "clearance_items": clearance_items,
99
- "overall_status": overall_status
100
- }
101
-
102
- # --- Student Management Routes (Likely for Frontend/Admin Use) ---
103
-
104
- @app.post("/api/students/", response_model=Student, status_code=status.HTTP_201_CREATED, summary="Create a new student")
105
- async def create_student(student: StudentCreate):
106
- """Creates a new student record using 'databases'."""
107
- # Check if student ID already exists using the crud function (which uses databases)
108
- existing_student_by_id = await crud.get_student_by_student_id(student.student_id)
109
- if existing_student_by_id:
110
- raise HTTPException(status_code=400, detail="Student ID already registered")
111
-
112
- # Check if tag ID already exists using the crud function (which uses databases)
113
- existing_student_by_tag = await crud.get_student_by_tag_id(student.tag_id)
114
- if existing_student_by_tag:
115
- raise HTTPException(status_code=400, detail="Tag ID already assigned to another student")
116
-
117
- # Create the student using the crud function (which uses databases)
118
- return await crud.create_student(student)
119
-
120
- @app.get("/api/students/", response_model=List[Student], summary="Get all students")
121
- async def get_students():
122
- """Retrieves a list of all students using 'databases'."""
123
- # Get all students using the crud function (which uses databases)
124
- return await crud.get_all_students()
125
-
126
- @app.get("/api/students/{student_id}", response_model=ClearanceDetail, summary="Get clearance details for a specific student")
127
- async def get_student_clearance(student_id: str):
128
- """
129
- Retrieves the full clearance details for a student based on their student ID
130
- using 'databases'. This endpoint is likely used by the frontend/admin interface.
131
- """
132
- # Get student info using the crud function (which uses databases)
133
- student = await crud.get_student_by_student_id(student_id)
134
-
135
- if not student:
136
- raise HTTPException(status_code=404, detail="Student not found")
137
-
138
- # Get clearance status using the crud function (which uses databases)
139
- statuses = await crud.get_clearance_statuses_by_student_id(student_id)
140
-
141
- # Format clearance items and determine overall status
142
- clearance_items = []
143
- overall_status = True
144
-
145
- for status_item in statuses:
146
- clearance_items.append({
147
- "department": status_item["department"],
148
- "status": status_item["status"],
149
- "remarks": status_item["remarks"],
150
- "updated_at": status_item["updated_at"].isoformat() # Format datetime to ISO string
151
- })
152
- if not status_item["status"]:
153
- overall_status = False
154
 
155
- # Return the formatted clearance details
156
- return {
157
- "student_id": student["student_id"],
158
- "name": student["name"],
159
- "department": student["department"],
160
- "clearance_items": clearance_items,
161
- "overall_status": overall_status
162
- }
163
-
164
- # --- Clearance Management Routes (Likely for Frontend/Admin Use) ---
165
-
166
- @app.post("/api/clearance/", response_model=ClearanceStatus, summary="Create or update a student's clearance status for a department")
167
- async def update_clearance_status(status_data: ClearanceStatusCreate):
168
- """
169
- Creates a new clearance status entry for a student and department,
170
- or updates an existing one using 'databases'.
171
- """
172
- # Check if student exists using the crud function (which uses databases)
173
- student = await crud.get_student_by_student_id(status_data.student_id)
174
- if not student:
175
- raise HTTPException(status_code=404, detail="Student not found")
176
-
177
- # Create or update the clearance status using the crud function (which uses databases)
178
- return await crud.create_or_update_clearance_status(status_data)
179
-
180
-
181
- # --- Root Endpoint (Optional) ---
182
- @app.get("/", summary="Root endpoint")
183
  async def read_root():
184
  """Basic root endpoint to confirm the API is running."""
185
  return {"message": "Undergraduate Clearance System API is running"}
186
 
187
 
188
- # --- Run the FastAPI app ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  if __name__ == "__main__":
190
- # Use uvicorn to run the FastAPI application
191
  uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
 
1
+ from fastapi import FastAPI, JSONResponse
2
+ from datetime import datetime
3
+ from fastapi.middleware.cors import CORSMiddleware # Keep if CORS is needed
4
  import uvicorn
5
+ from contextlib import asynccontextmanager
6
+
7
+
8
+ from src.database import create_db_tables, get_db
9
+ from src.routers import students, devices, clearance, token, users, admin
10
+
11
+ @asynccontextmanager
12
+ async def lifespan(app_instance: FastAPI):
13
+ """Handles application startup and shutdown events."""
14
+ print("Application startup...")
15
+ create_db_tables()
16
+ print("Database tables checked/created.")
17
+ yield
18
+ print("Application shutdown...")
19
+
20
 
21
  # FastAPI app instance
22
+ app = FastAPI(
23
+ title="Undergraduate Clearance System API",
24
+ description="""
25
+ A comprehensive API for managing student clearance processes with RFID authentication.
26
+
27
+ ## Features
28
+ * **RFID Authentication** - Students and staff use RFID tags for quick access
29
+ * **Multi-Department Clearance** - Support for Library, Bursary, Alumni, and Departmental clearances
30
+ * **Device Management** - ESP32 RFID readers with secure API key authentication
31
+ * **Role-Based Access** - Different permissions for Students, Staff, and Administrators
32
+ * **Real-Time Tracking** - Live clearance status updates and comprehensive logging
33
+
34
+ ## Authentication Methods
35
+ * **JWT Tokens** - For web interface and administrative access
36
+ * **RFID Tags** - For quick student and staff authentication
37
+ * **Device API Keys** - For ESP32 RFID reader authentication
38
+ """,
39
+ version="2.0.0",
40
+ lifespan=lifespan,
41
+ docs_url="/docs",
42
+ redoc_url="/redoc",
43
+ openapi_url="/openapi.json",
44
+ )
45
 
 
 
46
  app.add_middleware(
47
  CORSMiddleware,
48
+ allow_origins= ['*'],
49
  allow_credentials=True,
50
+ allow_methods=["*"], # Allows all methods
51
+ allow_headers=["*"], # Allows all headers
52
  )
53
 
54
+ # Include routers
55
+ app.include_router(devices.router)
56
+ app.include_router(students.router)
57
+ app.include_router(clearance.router)
58
+ app.include_router(token.router)
59
+ app.include_router(users.router)
60
+ app.include_router(admin.router)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ # Root endpoint
63
+ @app.get("/", summary="Root endpoint", tags=["Default"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  async def read_root():
65
  """Basic root endpoint to confirm the API is running."""
66
  return {"message": "Undergraduate Clearance System API is running"}
67
 
68
 
69
+ @app.get("/version", summary="API Version Information", tags=["System"])
70
+ async def get_version():
71
+ """Get detailed API version information."""
72
+ return {
73
+ "api_version": "2.0.0",
74
+ "last_updated": "2025-06-07",
75
+ "features": [
76
+ "RFID Authentication",
77
+ "Multi-Department Clearance",
78
+ "Device Management",
79
+ "Real-Time Tracking",
80
+ "Comprehensive Logging"
81
+ ],
82
+ "supported_authentication": [
83
+ "JWT Tokens",
84
+ "RFID Tags",
85
+ "Device API Keys"
86
+ ]
87
+ }
88
+
89
+ # Health check endpoints
90
+ @app.get("/health", summary="Health Check", tags=["System"])
91
+ async def health_check():
92
+ """Comprehensive health check endpoint."""
93
+ health_status = {
94
+ "status": "healthy",
95
+ "timestamp": datetime.utcnow().isoformat(),
96
+ "version": "2.0.0",
97
+ "uptime": "calculated_if_needed"
98
+ }
99
+
100
+ status_code = 200
101
+ return JSONResponse(
102
+ status_code=status_code,
103
+ content=health_status
104
+ )
105
+
106
+ # Run the FastAPI app using Uvicorn
107
  if __name__ == "__main__":
 
108
  uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
src/models.py CHANGED
@@ -9,9 +9,7 @@ import os
9
 
10
  Base = declarative_base()
11
 
12
- JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-default-secret-key-for-dev-only-CHANGE-ME")
13
- if JWT_SECRET_KEY == "your-default-secret-key-for-dev-only-CHANGE-ME":
14
- print("WARNING: Using default JWT_SECRET_KEY. Please set a strong JWT_SECRET_KEY environment variable for production.")
15
 
16
  # Enums - Values must EXACTLY match the labels in the PostgreSQL ENUM types, case-sensitively.
17
  class UserRole(str, enum.Enum):
 
9
 
10
  Base = declarative_base()
11
 
12
+ JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY")
 
 
13
 
14
  # Enums - Values must EXACTLY match the labels in the PostgreSQL ENUM types, case-sensitively.
15
  class UserRole(str, enum.Enum):