diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..ba2a6c013 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:system", + "python-envs.pythonProjects": [] +} \ No newline at end of file diff --git a/app.py b/app.py index d82c51f0d..27dd7fe27 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,113 @@ -from flask import Flask -app = Flask(__name__) +# IMPORTS +from flask import Flask, render_template, session, abort, redirect, url_for, request +import pathlib,os +from google.oauth2 import id_token +from google_auth_oauthlib.flow import Flow +from pip._vendor import cachecontrol +import google.auth.transport.requests +import requests -@app.route('/') -def hello_world(): - return 'Hello, World!' + +app = Flask("Studsight") +app.secret_key = "davidneastudsightkey.com" + +os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" + +client_secrets_file = os.path.join(pathlib.Path(__file__).parent, "client_secret.json") # Path to client secrets file +GOOGLE_CLIENT_ID = "771970138692-gjilmd2o08eitr81o07oiuhfe7m5ardh.apps.googleusercontent.com" # Your Google Client ID for OAuth 2.0 + +# Example initialization (update with your actual client secrets file and scopes) +#Links to the google oauth 2.0 server for authentication +flow = Flow.from_client_secrets_file( + client_secrets_file=client_secrets_file, + scopes=[ + "https://www.googleapis.com/auth/userinfo.profile", + "https://www.googleapis.com/auth/userinfo.email", + "openid" + ], + redirect_uri="https://nea-studsight.onrender.com/callback" # Your redirect URI once authentication is complete + + ) + +#Decorator to check if user is logged in before accessing certain routes +def login_is_required(function): + def wrapper(*args, **kwargs): + if "google_id" not in session: + abort(401) # Authorisation required + else: + return function() # Call the original function + + return wrapper + + + + +@app.route("/login") # Login route +def login(): + authorization_url, state = flow.authorization_url() + session["state"] = state + return redirect(authorization_url) + + +# @app.route("/callback") # Callback route +# def callback(): +# flow.fetch_token(authorization_response=request.url) + +# if not session["state"] == request.args["state"]: +# abort(500) # State does not match! + +# credentials = flow.credentials +# request_session = request.session() +# cached_session = cachecontrol.CacheControl(request_session) +# token_request = google.auth.transport.requests.Request(session=cached_session) + +# id_info = id_token.verify_oauth2_token( +# id_token=credentials._id_token, +# request=token_request, +# audience=GOOGLE_CLIENT_ID +# ) +# return id_info + + + +@app.route("/callback") # Callback route to handle Google's response +def callback(): + flow.fetch_token(authorization_response=request.url) + if not session["state"] == request.args["state"]: + abort(500) + credentials = flow.credentials + request_session = requests.session() + cached_session = cachecontrol.CacheControl(request_session) + token_request = google.auth.transport.requests.Request(session=cached_session) + id_info = id_token.verify_oauth2_token( + id_token=credentials._id_token, + request=token_request, + audience=GOOGLE_CLIENT_ID + ) + session["google_id"] = id_info.get("sub") + session["email"] = id_info.get("email") + return redirect("/protected_area") + +@app.route('/autho') +def autho(): + return redirect("/protected_area") + +@app.route("/logout") # Logout route to clear session +def logout(): + session.clear() + return redirect("/") + + +@app.route("/") # Home route which is the landing page when the app is accessed +def home(): + return render_template("home.html") + +#Protected area route, only accessibl after login +@app.route("/protected_area") # This is where the people who have access to the app will go to after loggin in to view the app's main content +@login_is_required +def protected_area(): + return render_template("protected_area.html", email=session["google_id"]) + + +if __name__ == "__main__": # Run the app + app.run(debug=True) diff --git a/client_secret.json b/client_secret.json new file mode 100644 index 000000000..e597839d8 --- /dev/null +++ b/client_secret.json @@ -0,0 +1,13 @@ +{ + "web": { + "client_id": "771970138692-gjilmd2o08eitr81o07oiuhfe7m5ardh.apps.googleusercontent.com", + "project_id": "nea-studsight", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "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", + "redirect_uris": [ + "https://nea-studsight.onrender.com/callback" + ] + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 147ddd086..e7f67afc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,129 @@ Flask +SQLAlchemy +PyInputPlus Gunicorn +google-api-python-client +google-auth +google-auth-oauthlib +google-auth-httplib2 +anyio==4.9.0 +argon2-cffi==25.1.0 +argon2-cffi-bindings==21.2.0 +arrow==1.3.0 +asttokens==3.0.0 +async-lru==2.0.5 +attrs==25.3.0 +babel==2.17.0 +beautifulsoup4==4.13.4 +bleach==6.2.0 +certifi==2025.7.9 +cffi==1.17.1 +charset-normalizer==3.4.2 +colorama==0.4.6 +comm==0.2.2 +contourpy==1.3.2 +cycler==0.12.1 +debugpy==1.8.14 +decorator==5.2.1 +defusedxml==0.7.1 +executing==2.2.0 +fastjsonschema==2.21.1 +filelock==3.13.1 +fonttools==4.58.5 +fqdn==1.5.1 +fsspec==2024.6.1 +gitdb==4.0.12 +GitPython==3.1.44 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==3.10 +ipykernel==6.29.5 +ipython==9.4.0 +ipython_pygments_lexers==1.1.1 +isoduration==20.11.0 +jedi==0.19.2 +Jinja2==3.1.6 +joblib==1.5.1 +json5==0.12.0 +jsonpointer==3.0.0 +jsonschema==4.24.0 +jsonschema-specifications==2025.4.1 +jupyter-events==0.12.0 +jupyter-lsp==2.2.5 +jupyter-server-mathjax==0.2.6 +jupyter_client==8.6.3 +jupyter_core==5.8.1 +jupyter_server==2.16.0 +jupyter_server_terminals==0.5.3 +jupyterlab==4.4.4 +jupyterlab_git==0.51.2 +jupyterlab_pygments==0.3.0 +jupyterlab_server==2.27.3 +kiwisolver==1.4.8 +MarkupSafe==3.0.2 +matplotlib==3.10.3 +matplotlib-inline==0.1.7 +mistune==3.1.3 +mpmath==1.3.0 +narwhals==1.46.0 +nbclient==0.10.2 +nbconvert==7.16.6 +nbdime==4.0.2 +nbformat==5.10.4 +nest-asyncio==1.6.0 +networkx==3.3 +notebook_shim==0.2.4 +numpy==2.3.1 +overrides==7.7.0 +packaging==25.0 +pandas==2.3.1 +pandocfilters==1.5.1 +parso==0.8.4 +pexpect==4.9.0 +pillow==11.3.0 +platformdirs==4.3.8 +plotly==6.2.0 +prometheus_client==0.22.1 +prompt_toolkit==3.0.51 +psutil==7.0.0 +ptyprocess==0.7.0 +pure_eval==0.2.3 +pycparser==2.22 +Pygments==2.19.2 +pyparsing==3.2.3 +python-dateutil==2.9.0.post0 +python-json-logger==3.3.0 +pytz==2025.2 +PyYAML==6.0.2 +pyzmq==27.0.0 +referencing==0.36.2 +requests==2.32.4 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rpds-py==0.26.0 +scikit-learn==1.7.0 +scipy==1.16.0 +seaborn==0.13.2 +Send2Trash==1.8.3 +setuptools==80.9.0 +six==1.17.0 +smmap==5.0.2 +sniffio==1.3.1 +soupsieve==2.7 +stack-data==0.6.3 +sympy==1.13.3 +terminado==0.18.1 +threadpoolctl==3.6.0 +tinycss2==1.4.0 +tornado==6.5.1 +traitlets==5.14.3 +types-python-dateutil==2.9.0.20250708 +typing_extensions==4.14.1 +tzdata==2025.2 +uri-template==1.3.0 +urllib3==2.5.0 +wcwidth==0.2.13 +webcolors==24.11.1 +webencodings==0.5.1 +websocket-client==1.8.0 diff --git a/static/home.css b/static/home.css new file mode 100644 index 000000000..e92ec9a27 --- /dev/null +++ b/static/home.css @@ -0,0 +1,171 @@ +body { + font-family: 'Segoe UI', Arial, sans-serif; + background: linear-gradient(135deg, #e0e7ff 0%, #f4f6fb 100%); + margin-top: 5vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +/* .main-container { + max-width: 1500px; + width: 1500px; + margin: 0 auto; +} */ + + +.container { + background-image: url('school2.jpg'); + background-size: cover; + background-repeat: no-repeat; + padding: 2.5rem 3.5rem; + border-radius: 18px; + box-shadow: 0 6px 32px rgba(79,140,255,0.10), 0 1.5px 6px rgba(0,0,0,0.06); + text-align: align; + position: relative; + z-index: 2; + max-width: 85vw; + width: 85vw; +} + + +h1 { + margin-bottom: 1.2rem; + color: #2d3a4b; + font-size: 2.5rem; + letter-spacing: 1px; +} +.subtitle { + color: #f5981e; + font-size: 1.2rem; + margin-bottom: 3rem; +} +.signin-btn { + background: linear-gradient(90deg, #4f8cff 60%, #6ee7b7 100%); + color: #fff; + position: relative; + left: 80%; + align-items: right; + size: max-content; + height: 5vw; + width: 15vw; + border: none; + padding: 0.9rem 2.5rem; + border-radius: 8px; + font-size: 2rem; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px rgba(79,140,255,0.10); + transition: background 0.2s, transform 0.2s; +} +.signin-btn:hover { + background: linear-gradient(90deg, #376fd0 60%, #34d399 100%); + transform: translateY(-2px) scale(1.04); +} +.hero-img { + width: max-content; + max-width: 80vw; + left: 75%; + position: relative; + align-self: right; + margin-bottom: 0rem; + filter: drop-shadow(0 4px 16px rgba(79,140,255,0.12)); +} + +.hero { + position: relative; + bottom: 80%; + display: flex; + flex-direction: column; + align-items: right; + text-align: right; +} + +.features { + display: flex; + flex-wrap: wrap; + justify-content: right; + gap: 2rem; + margin-top: 2.5rem; + width: 100%; +} +.feature-card { + background: #f0f7ff; + border-radius: 12px; + box-shadow: 0 1px 4px rgba(79,140,255,0.07); + padding: 1.2rem 1.5rem; + position: relative; + width: 10vw; + max-width: 90vw; + text-align: right; + display: flex; + flex-direction: column; + align-items: flex-start; + transition: box-shadow 0.2s, transform 0.2s; +} +.feature-card:hover { + box-shadow: 0 4px 16px rgba(79,140,255,0.15); + transform: translateY(-4px) scale(1.03); +} +.feature-icon { + font-size: 2.2rem; + margin-bottom: 0.7rem; +} +@media (max-width: 700px) { + .container { + padding: 1.2rem 0.5rem; + width: 98vw; + max-width: 98vw; + } + .features { + flex-direction: column; + gap: 1.2rem; + width: 100%; + } + .feature-card { + width: 100%; + max-width: 98vw; + } + h1 { + font-size: 2rem; + } + .subtitle { + font-size: 1rem; + } + .signin-btn { + padding: 0.7rem 1.2rem; + font-size: 1rem; + } + .hero-img { + width: 120px; + max-width: 80vw; + } + .container { + padding: 0.5rem 0.1rem; + width: 99vw; + max-width: 99vw; + } + .feature-card { + padding: 0.7rem 0.5rem; + } + h1 { + font-size: 1.3rem; + } + .subtitle { + font-size: 0.9rem; + } + .hero-img { + width: 80px; + } +} + +.background-svg { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 0; + pointer-events: none; +} diff --git a/static/protected_area.css b/static/protected_area.css new file mode 100644 index 000000000..3989ce293 --- /dev/null +++ b/static/protected_area.css @@ -0,0 +1,33 @@ + +body { + background: #f5f7fa; + font-family: Arial, sans-serif; + margin: 0; + padding: 0; +} +.container { + max-width: 400px; + margin: 80px auto; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.08); + padding: 32px 24px; + text-align: center; +} +h1 { + color: #333; + margin-bottom: 24px; +} +.signout-btn { + background: #e74c3c; + color: #fff; + border: none; + padding: 12px 28px; + border-radius: 4px; + font-size: 16px; + cursor: pointer; + transition: background 0.2s; +} +.signout-btn:hover { + background: #c0392b; +} diff --git a/static/school2.jpg b/static/school2.jpg new file mode 100644 index 000000000..14e45aa90 Binary files /dev/null and b/static/school2.jpg differ diff --git a/static/schoolback.jpg b/static/schoolback.jpg new file mode 100644 index 000000000..482c1927e Binary files /dev/null and b/static/schoolback.jpg differ diff --git a/student.db b/student.db new file mode 100644 index 000000000..4c03643a4 Binary files /dev/null and b/student.db differ diff --git a/student.py b/student.py new file mode 100644 index 000000000..27118d57a --- /dev/null +++ b/student.py @@ -0,0 +1,70 @@ +import sqlite3, os, time, pyinputplus as pyip + +connection = sqlite3.connect("student.db") +cursor = connection.cursor() + +cursor.execute('''CREATE TABLE IF NOT EXISTS Students + (StudentID INTEGER PRIMARY KEY AUTOINCREMENT,Firstname TEXT, + Surname TEXT, Age INTEGER, Gender TEXT, Mastery TEXT, + Yeargroup INTEGER, Email TEXT)''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Timetable + ( StudentID INTEGER, Day TEXT, Period1 TEXT, Period2 TEXT, + Period3 TEXT, Period4 TEXT, Period5 TEXT, Period6 TEXT, + FOREIGN KEY(StudentID) REFERENCES students(StudentID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Student_Info + (StudentID INTEGER, Parentname TEXT, Parentnumber INTEGER, + Address TEXT, Nationality TEXT, countryofbirth TEXT, Enrollmentdate TEXT, + FOREIGN KEY(StudentID) REFERENCES students(StudentID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Medical_Info + (StudentID INTEGER, Conditions TEXT, Medication TEXT, Allergies TEXT, + Needs TEXT, FOREIGN KEY(StudentID) REFERENCES students(StudentID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Attendance + (AttendanceID INTEGER PRIMARY KEY AUTOINCREMENT, StudentID INTEGER, + Date TEXT, Status TEXT, FOREIGN KEY(StudentID) REFERENCES students(StudentID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Behaviour + (BehaviourID INTEGER PRIMARY KEY AUTOINCREMENT, StudentID INTEGER, + Date TEXT, Housepoints INTEGER, Sanctions TEXT, Action TEXT, + FOREIGN KEY(StudentID) REFERENCES students(StudentID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Teachers + (TeacherID INTEGER PRIMARY KEY AUTOINCREMENT, Firstname TEXT, + Gender TEXT, Surname TEXT, Email TEXT)''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Subjects + (SubjectID INTEGER PRIMARY KEY AUTOINCREMENT, Subjectname TEXT, + TeacherID INTEGER, FOREIGN KEY(TeacherID) REFERENCES teachers(TeacherID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Scores + (ScoreID INTEGER PRIMARY KEY AUTOINCREMENT, StudentID INTEGER, SubjectID INTEGER, + Score INTEGER, Assessment1 FLOAT, Assessment2 FLOAT, Assessment3 FLOAT, + FOREIGN KEY(StudentID) REFERENCES students(StudentID), + FOREIGN KEY(SubjectID) REFERENCES subjects(SubjectID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Assessments + (AssessmentID INTEGER PRIMARY KEY AUTOINCREMENT, StudentID INTEGER, + SubjectID INTEGER, Type TEXT, Score FLOAT, Date TEXT, + FOREIGN KEY(StudentID) REFERENCES students(StudentID), + FOREIGN KEY(SubjectID) REFERENCES subjects(SubjectID))''') + + +cursor.execute('''CREATE TABLE IF NOT EXISTS Summaries + (SummaryID INTEGER PRIMARY KEY AUTOINCREMENT, StudentID INTEGER, + Week INTEGER, SummaryText TEXT, + FOREIGN KEY(StudentID) REFERENCES students(StudentID))''') + + +connection.commit() diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 000000000..be5c7bfb9 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,35 @@ + + +
+ +You are logged in
+