diff --git a/ContainerContents/NoSheet.py b/ContainerContents/NoSheet.py new file mode 100644 index 0000000..0d1d118 --- /dev/null +++ b/ContainerContents/NoSheet.py @@ -0,0 +1,141 @@ +import openpyxl +import yaml +import datetime +from openpyxl.styles import Font, Border, Side, PatternFill +from openpyxl.formatting.rule import FormulaRule + + +def NoSheet(file_path): + """ + Creates the Template for more data to be added + + ``REQUIRES``: ``None`` Ensure no other sheets are present, will overwrite them + + ``PROMISES``: ``XLSX File`` give the template for recuitment for the year + + ``Develop in part by``: Brock + + ``Contact``: darkicewolf50@gmail.com + + """ + + yamlraw = """ +Recruitment Responses: + - Frist Name (What we should call them): Steve + - Last Name: the Bug + - Ucalgary Email: steve.the.bug@ucalgary.ca + - What Subsystem/SubTeam are you interested in?: | + Chassis + Ergonomics + Suspension + Steering + Powertrain + Final Drive + Any Mechanical + Business - Content Creation + Business - Business Relations + Software + - Major: General (1st Year) + - Academic Year: 1st + - Why are you interested in joining UCalgary BAJA?: Example Interest + - Where did you hear about us?: Testing + - Are you available for team meetings/work days? Saturdays 10 am - 4 pm: "No" #add condiftional formatting for no to make whole line red +Interview TimeTable: + - Date: 2024-09-16 + - Meeting Duration: 30 min + - Start Time Slot: 10:00:00 AM + - Slot: 1 + - Interviewee Name (What to call them): Steve + - Interviewee Email: steve.the.bug@ucalgary.ca + - Category (if not general): Test + - Interviewer(s) Name(s): Example + - Status: Dropdown (Options in datahelp) #default is Unknown +Data Helper And Info: + - Status Dropdown: + - Unknown + - Done + - No Show + - Cancelled/Moved + - First time Startup: Move docker volume pointer to new drive and start up container + - Weird Date: Add more space and it will change from ### to a date + - How to Add Dropdown: Go into data, click data validation, select list then select the area you want to get values from in the formula spot + """ + # uses the base above "yaml file" to create the base template + yamlsheet = yaml.safe_load(yamlraw) + + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + file_name = file_path + + # border style + border = Border( # defualt behaviour is thin + left=Side(style='thin'), + right=Side(style='thin'), + top=Side(style='thin'), + bottom=Side(style='thin') + ) + + # create workbook in memory + work_book = openpyxl.Workbook() + + # remove default sheet + default_sheet = work_book.active + work_book.remove(default_sheet) + + # decomposes the yaml file at the top and convertss it into a standard template + # does one sheet at a time + for sheet_name, title_list in yamlsheet.items(): + # add 1 standard sheet, by the outermost name + sheet = work_book.create_sheet(sheet_name) + + # gets header titles for each sheet, from the inner list + titles = [list(title.keys())[0] for title in title_list] + + # makes the header titles to bold, have a border and have text + for col_num, title in enumerate(titles, start=1): + cell = sheet.cell(row=1, column=col_num) + cell.value = title + cell.font = Font(bold=True) + cell.border = border + + # example data to show on what it will look like or to copy formatting down + example_data = [list(data.values())[0] for data in title_list] + + for col_num, data in enumerate(example_data, start=1): + + # for special case Data Helper where there a list in a dictionary + if isinstance(data, list): + row_num = 2 + for item in data: + cell = sheet.cell(row=row_num, column=col_num) + cell.value = item + row_num += 1 + # adds data to the cells + else: + cell = sheet.cell(row=2, column=col_num) + # changes the Dropdown data in status to unknown instead of the other option, only there for prep for a dropdown + if data == "Dropdown (Options in datahelp)": + cell.value = "Unknown" + + elif isinstance(data, datetime.date): + cell.value = data + # Convert the example date '2024-09-16' to a datetime object + # Set the number format to 'YYYY-MM-DD' + cell.number_format = 'yyyy-mmm-d' + else: + cell.value = data + + + if sheet.title == "Recruitment Responses": + sheet.conditional_formatting.add("A2:I2", FormulaRule(formula=['=$I2="No"'], fill=PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid"))) + + + # save to storage + work_book.save(file_name) + print(f"Created {file_name} for {year_donation}") + +if __name__ == "__main__": + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + # name based off the 2025 naming system + # Define the path to the Excel file and the lock file + file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" + NoSheet(file_name) \ No newline at end of file diff --git a/ContainerContents/ReadDB.py b/ContainerContents/ReadDB.py new file mode 100644 index 0000000..fdb1fc3 --- /dev/null +++ b/ContainerContents/ReadDB.py @@ -0,0 +1,91 @@ +import pandas as pd +import json +from filelock import FileLock, Timeout +import time + +from NoSheet import NoSheet +import datetime + +""" +TODO change to use new tempate +TODO change names to be more clear +""" + +def ReadDatabase(file_path): + """ + Reads the database for which slots are available + + ``REQUIRES``: ``File_Path`` where the file is + + ``PROMISES``: ``JSON`` Interview Available Slots + + ``Developed in part by``: Ahmad, Brock + + ``Contact``: ahmad.ahmad1@ucalgary.ca, darkicewolf50@gmail.com + + """ + + + # Define the path to the Excel file and the lock file + excel_file_path = file_path + lock_file_path = file_path + ".lock" + + # Retry parameters + max_retries = 60 # Maximum number of retries if the file is locked + retry_interval = 0.5 # Wait time (in seconds) between retries + + retries = 0 + while retries < max_retries: + try: + # Attempt to acquire a shared read (non-blocking) access + with FileLock(lock_file_path, timeout=0): # Non-blocking, checks if the lock exists + # Load the Excel file into a pandas DataFrame + df = pd.read_excel(excel_file_path, sheet_name="Interview TimeTable") + + # Initialize the dictionary to store the structured data + interview_data = {} + + # Group the DataFrame by Date, Start Time, and Slot for organization + for _, row in df.iterrows(): + date = str(row['Date']) + start_time = str(row['Start Time Slot']) + slot = int(row['Slot']) if not pd.isna(row['Slot']) else 0 + + # Returns the number of interviewees in the slot; returns 0 if empty + interviewee_amount = len(str(row['Interviewee Name (What to call them)']).split()) if str(row['Interviewee Name (What to call them)']) != "nan" else 0 + + # Check if the slot is available for an interviewee to attend + available_slots = interviewee_amount != slot + if available_slots: + # Initialize nested structure if not present + if date not in interview_data: + interview_data[date] = {} + # Add the start time and duration if not present + if start_time not in interview_data[date]: + interview_data[date][start_time] = { + 'Meeting Duration': row['Meeting Duration'], + } + return interview_data # Successfully read the database + + except Timeout: + # File is locked; wait and retry + retries += 1 + print(f"File is locked, retrying ({retries}/{max_retries})...") + time.sleep(retry_interval) + + # If max retries are exceeded, raise an error + raise RuntimeError("Unable to access the database after multiple attempts due to a file lock.") + + +# Example usage of the ReadDatabase function +if __name__ == "__main__": + import datetime + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + # name based off the 2025 naming system + # Define the path to the Excel file and the lock file + file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" + try: + data = ReadDatabase(file_name) + print(json.dumps(data, indent=4)) + except RuntimeError as e: + print(e) diff --git a/ContainerContents/WriteDB.py b/ContainerContents/WriteDB.py new file mode 100644 index 0000000..e9329a8 --- /dev/null +++ b/ContainerContents/WriteDB.py @@ -0,0 +1,157 @@ +import pandas as pd +import json +from openpyxl import load_workbook +from send_email import send_email +from filelock import FileLock + + +""" +TODO make it work with the new template +TODO update names to be more clear +TODO try to remove pandas +""" +def ReadDatabase(file_path, lock_path): + """ + Reads the Database to retrieve available interview slots + + ``REQUIRES``: ``File_Path`` ``Lock_Path`` where the file and lock are located + + ``PROMISES``: JSON (Available interview slots) + + ``Developed by``: Ahmad, Brock + + ``Contact``: ahmad.ahmad1@ucalgary.ca, darkicewolf50@gmail.com + """ + + # Use a file-based lock for thread-safe and process-safe access + with FileLock(lock_path): + # Load the Excel file into a pandas DataFrame with specific columns + df = pd.read_excel(file_path, usecols=['Date', 'Start Time Slot', 'Slot', 'Interviewee Name (What to call them)', 'Interviewee Email', 'Meeting Duration'], sheet_name="Interview TimeTable") + + # Initialize the dictionary to store structured data for available slots + interview_data = {} + + # Process each row in the DataFrame to structure data by date and time + for _, row in df.iterrows(): + # Convert Date and Start Time to string format for easier comparison + date = str(row['Date']).split(" ")[0] # Format date to YYYY-MM-DD + start_time = str(row['Start Time Slot']) + + # Calculate the slot capacity and current number of interviewees + slot_capacity = int(row['Slot']) if not pd.isna(row['Slot']) else 0 + interviewee_names = [name.strip() for name in str(row['Interviewee Name (What to call them)']).split(',') if name.strip()] + interviewee_count = len(interviewee_names) if interviewee_names != ["nan"] else 0 + + # Check if there are available slots for more interviewees + if interviewee_count < slot_capacity: + # Organize data by date and time, keeping track of available slots and meeting duration + if date not in interview_data: + interview_data[date] = {} + interview_data[date][start_time] = { + 'Meeting Duration': row['Meeting Duration'], + 'Available Slots': slot_capacity - interviewee_count + } + + return interview_data + +def AppendAppointment(file_path, date, start_time, interviewee_name, interviewee_email): + """ + Appends a new appointment with the interviewee's name and email if the slot is available. + + ``REQUIRES``: ``File_Path`` ``str`` date, ``str`` start_time, ``str`` interviewee_name, ``str`` interviewee_email + + ``PROMISES``: ``None`` Updates the Excel file with the new interviewee's name and email if there is an available slot. Returns Bool. + + ``Developed by``: Ahmad, Brock + + ``Contact``: ahmad.ahmad1@ucalgary.ca, darkicewolf50@gmail.com + """ + + lock_path = file_path + ".lock" + + available_slots = ReadDatabase(file_path, lock_path) + + # Check if the requested slot is available in the `available_slots` structure + if date in available_slots and start_time in available_slots[date]: + with FileLock(lock_path): # Ensure process-safe access to the file + # Load workbook and select "Interview TimeTable" for updating appointments + workbook = load_workbook(file_path) + sheet = workbook["Interview TimeTable"] + df = pd.read_excel(file_path, sheet_name="Interview TimeTable") + + # Find and update the row that matches the provided date and start time + for index, row in df.iterrows(): + row_date = str(row['Date']).split(" ")[0] + row_start_time = str(row['Start Time Slot']) + + if row_date == date and row_start_time == start_time: + # Current entries for names and emails, and append new data with comma and space + current_names = str(row['Interviewee Name (What to call them)']).strip() + current_emails = str(row['Interviewee Email']).strip() + + updated_names = f"{current_names}, {interviewee_name}" if current_names != "nan" else interviewee_name + updated_emails = f"{current_emails}, {interviewee_email}" if current_emails != "nan" else interviewee_email + + # Update the cells with new names and emails + name_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Name (What to call them)') + 1) + email_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Interviewee Email') + 1) + name_cell.value = updated_names + email_cell.value = updated_emails + + status_cell = sheet.cell(row=index + 2, column=df.columns.get_loc('Status') + 1) + status_cell.value = "Unknown" + + workbook.save(file_path) + send_email(interviewee_email, interviewee_name, date, start_time) + return True + + # If no slots available, return that the slot is unavailable + return False + + +def run_tests(): + """ + Executes test cases to verify appointment scheduling and slot availability. + + ``REQUIRES``: None + + ``PROMISES``: Prints test outcomes to validate successful booking or slot unavailability. + """ + import datetime + year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season + # name based off the 2025 naming system + # Define the path to the Excel file and the lock file + file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" + lock = file_name + ".lock" + + print("Available Slots:") + available_slots = ReadDatabase(file_path=file_name, lock_path=lock) + print(json.dumps(available_slots, indent=4)) + + # Test Case 1: Append to an available slot on 2024-09-16 at 10:30:00 + date_1 = "9/16/2024" + start_time_1 = "10:30:00" + interviewee_name_1 = "Alice Johnson" + interviewee_email_1 = "ahmadmuhammadofficial@gmail.com" + print(f"\nTest Case 1: Trying to book {date_1} at {start_time_1} for {interviewee_name_1} ({interviewee_email_1})") + AppendAppointment(file_name, date_1, start_time_1, interviewee_name_1, interviewee_email_1) + + # Test Case 2: Append to an available slot on 2024-09-17 at 13:30:00 + date_2 = "9/17/2024" + start_time_2 = "13:30:00" + interviewee_name_2 = "Bob Smith" + interviewee_email_2 = "bob.smith@example.com" + print(f"\nTest Case 2: Trying to book {date_2} at {start_time_2} for {interviewee_name_2} ({interviewee_email_2})") + AppendAppointment(file_name, date_2, start_time_2, interviewee_name_2, interviewee_email_2) + + # Test Case 3: Attempting to book at 10:30:00 on 2024-09-16 for a different interviewee + date_3 = "9/16/2024" + start_time_3 = "10:30:00" + interviewee_name_3 = "Charlie Brown" + interviewee_email_3 = "charlie.brown@example.com" + print(f"\nTest Case 3: Trying to book {date_3} at {start_time_3} for {interviewee_name_3} ({interviewee_email_3})") + AppendAppointment(file_name, date_3, start_time_3, interviewee_name_3, interviewee_email_3) + +# Run tests +if __name__ == "__main__": + run_tests() diff --git a/ContainerContents/interviewPackagers.py b/ContainerContents/interviewPackagers.py new file mode 100644 index 0000000..f036977 --- /dev/null +++ b/ContainerContents/interviewPackagers.py @@ -0,0 +1,62 @@ +from ReadDB import ReadDatabase + + +def getSchedulePackager(file_name): + """ + Packages up the response for a http response + + ``REQUIRES``: ``File_Path`` where the file is + + ``PROMISES``: ``JSON`` http response ready + + ``Develop in part by``: Brock T + + ``Contact``: darkicewolf50@gmail.ocm + + """ + return { + "interviewDates": ReadDatabase(file_path=file_name) + } + +from WriteDB import AppendAppointment +from email_validator import validate_email, EmailNotValidError + + +def SelectAppointment (file_name, appointmentJson): + """ + Packages up a response for a http request + + ``REQUIRES``: ``File_Path`` ``JSON`` where the file is, json has the data of interviewee name, date, starttime and interviewee email + + ``PROMISES``: ``JSON`` Returns if the booking was a success + + ``Developed in part by``: Brock + + ``Contact``: darkicewolf50@gmail.com + + """ + """ + Example of an incoming http post body + { + "intervieweeName": "Brock", + "date": "9/16/2024", + "startTime": "11:00:00", + "intervieweeEmail": "darkicewolf50@gmail.com" + } + """ + try: + validEmail = validate_email(appointmentJson["intervieweeEmail"], check_deliverability=True) + if validEmail: + status = AppendAppointment(file_path=file_name, date=appointmentJson["date"], start_time=appointmentJson["startTime"], interviewee_name=appointmentJson["intervieweeName"], interviewee_email=appointmentJson["intervieweeEmail"]) + + if status: + resBody = {"Success": True, "validEmail": "true"} + else: + resBody = {"Success": False, "validEmail": "true"} + + # resBody["message"] = appointmentJson for testing + return resBody + + except EmailNotValidError as e: + return {"Success": False, "validEmail": "false"} + diff --git a/ContainerContents/main.py b/ContainerContents/main.py new file mode 100644 index 0000000..7b82e46 --- /dev/null +++ b/ContainerContents/main.py @@ -0,0 +1,128 @@ +from fastapi import FastAPI +from fastapi.responses import JSONResponse +from pydantic import BaseModel + +from NoSheet import NoSheet +import datetime +import os + +year_donation = int(str(datetime.datetime.now().year)[2:]) + 1 # gets the last two digits of the current year then adds 1 for the current season +# name based off the 2025 naming system +# Define the path to the Excel file and the lock file +file_name = f"./Interviews/OR{year_donation}-L-Interview Data.xlsx" +if not os.path.isfile(file_name): + os.makedirs(os.path.dirname(file_name), exist_ok=True) + NoSheet(file_name) + +app = FastAPI() + +@app.get("/") +def get_root(): + """ + This does nothing, allows for pings to check for life + + ``REQUIRES``: ```None`` Nothing + + ``PROMISES``: ``JSON`` returns a short message in the body + + ``Develop in part by``: Brock + + ``Contact``: darkicewolf50@gmail.com + + """ + + res = {"message": "Hello I am alive, this does nothing"} + + # Return the response with the custom header + return JSONResponse( + headers={ + "isBase64Encoded": "false", # Header Modification + }, + content={ + "body": res # Ensure res is a dict or do json.dumps to enusre it is stringified + }, + + # status_code=200 commented out just to show how to change it if you wanted + ) + + +from interviewPackagers import getSchedulePackager + +@app.get("/getAppointments") +async def getAppointments(): + """ + checks for all available slots in the database + + ``REQUIRES``: ``None`` Nothing + + ``PROMISES``: ``JSON`` returns all of the avaialbe slots by date then time + + ``Develop in part by``: Brock + + ``Contact``: darkicewolf50@gmail.com + + """ + + res = getSchedulePackager(file_name) + + return JSONResponse( + headers={ + "isBase64Encoded": "false", # Header Modification + }, + content={ + "body": res # Ensure res is a dict or do json.dumps to enusre it is stringified + }, + + # status_code=200 commented out just to show how to change it if you wanted + ) + +from interviewPackagers import SelectAppointment + +class Appointment(BaseModel): + """ + The formatted + + ``REQUIRES``: Correct Format + + ``PROMISES``: Formatted class, needs to be converted into dict os that it can be used in another file + + ``Develop in part by``: Brock + + ``Contact``: darkicewolf50@gmail.com + + """ + intervieweeName: str + date: str + startTime: str + intervieweeEmail: str + + +@app.post("/SelectInterview") +async def postSelectInterview(rawRequest: Appointment): + """ + Books an interview, first checks if the slot is valid + + ``REQUIRES``: ``Appointment`` A specifically formatted request + + ``PROMISES``: ``JSON`` returns if the booking was successful or not + + ``Develop in part by``: Brock + + ``Contact``: darkicewolf50@gmail.com + + """ + + requestDict = {key: str(value) for key, value in rawRequest.dict().items()} + res = SelectAppointment(file_name, requestDict) + + return JSONResponse( + headers={ + "isBase64Encoded": "false", # Header Modification + }, + content={ + "body": res # Ensure res is a dict or do json.dumps to enusre it is stringified + }, + + # status_code=200 commented out just to show how to change it if you wanted + ) + diff --git a/ContainerContents/requirements.txt b/ContainerContents/requirements.txt new file mode 100644 index 0000000..5b1ff2d --- /dev/null +++ b/ContainerContents/requirements.txt @@ -0,0 +1,4 @@ +fastapi[standard] +pandas +openpyxl +PyYAML \ No newline at end of file diff --git a/ContainerContents/send_email.py b/ContainerContents/send_email.py new file mode 100644 index 0000000..a506b71 --- /dev/null +++ b/ContainerContents/send_email.py @@ -0,0 +1,94 @@ +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from datetime import datetime, timedelta +import pytz # For timezone handling + +""" +TODO add +""" +def send_email(interviewee_email="darkicewolf50@gmail.com", interviewee_name="brock", date="2024-1-10", start_time="10:00:00", location ="ENC25"): + """ + Sends an email notification to the interviewee and to the uofcbaja account + + ``REQUIRES``: ``str`` interviewee_email, ``str`` interviewee_name, ``str`` date, ``str`` start_time + + ``PROMISES``: ``EMAIL`` Sends an email to interviewee and static email on successful appointment booking. + + ``Developed by``: Ahmad + + ``Contact``: ahmad.ahmad1@ucalgary.ca + """ + # Define static email for notifications and Gmail credentials + static_email = "uofcbaja.noreply@gmail.com" + gmail_user = "uofcbaja.noreply@gmail.com" + gmail_apppassword = "pver lpnt upjd zvld" + + # Define Mountain Standard Time + mst = pytz.timezone("America/Edmonton") # MST with Daylight Savings considered + + # Parse the input date and time, localize to MST + start_datetime = mst.localize(datetime.strptime(f"{date} {start_time}", "%Y-%m-%d %H:%M:%S")) + end_datetime = start_datetime + timedelta(minutes=30) + + # Format date and time for the calendar URLs + start_time_google = start_datetime.strftime("%Y%m%dT%H%M%S") # Google Calendar (Local time without Z) + end_time_google = end_datetime.strftime("%Y%m%dT%H%M%S") # Google Calendar (Local time without Z) + outlook_start = start_datetime.isoformat() # Outlook Calendar (ISO local time) + outlook_end = end_datetime.isoformat() # Outlook Calendar (ISO local time) + + # Create message object + msg = MIMEMultipart() + msg['From'] = gmail_user + msg['To'] = f"{interviewee_email}, {static_email}" + msg['Subject'] = "Interview Appointment Confirmation" + + # Message body + body = f''' + + + Interview Invitation + + +

Dear {interviewee_name},

+

Your interview has been scheduled on {date} at {start_time} MST.

+

Your interview location is at {location} or will be emailed to you.

+

Please ensure to be available at the designated time.

+ + + + + + +

Best regards,

+

UCalgary Baja Interview Team

+ UCalgary Baja Team + + + ''' + + msg.attach(MIMEText(body, 'html')) + + try: + # Setup the server and send the email + server = smtplib.SMTP('smtp.gmail.com', 587) + server.starttls() + server.login(gmail_user, gmail_apppassword) + server.sendmail(gmail_user, [interviewee_email, static_email], msg.as_string()) + server.quit() + # print(f"Email sent successfully to {interviewee_email} and {static_email}.") + except Exception as e: + print(f"Failed to send email: {e}") + +if __name__ == "__main__": + send_email() diff --git a/Docker Test/Dockerfile b/Docker Test/Dockerfile new file mode 100644 index 0000000..d924b8a --- /dev/null +++ b/Docker Test/Dockerfile @@ -0,0 +1,17 @@ +# Use an official Python runtime as a parent image +FROM python:3.10-slim + +# Set the working directory inside the container +WORKDIR /app + +# Copy the current directory contents into the container at /app +COPY ./mock /app + +# Install any necessary dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Expose port 8080 for the container to listen on +EXPOSE 8080 + +# Command to run the Python server when the container starts +CMD ["python", "server.py"] diff --git a/Docker Test/docker-compose.yaml b/Docker Test/docker-compose.yaml new file mode 100644 index 0000000..a24300e --- /dev/null +++ b/Docker Test/docker-compose.yaml @@ -0,0 +1,10 @@ +services: + test-http-container: + container_name: test-http-container + ports: + - 8080:8080 + volumes: + - ./mock:/app + build: . + # networks: + # - testnet diff --git a/Docker Test/dockertest-test-http-container_latest.tar.gz b/Docker Test/dockertest-test-http-container_latest.tar.gz new file mode 100644 index 0000000..292086d Binary files /dev/null and b/Docker Test/dockertest-test-http-container_latest.tar.gz differ diff --git a/Docker Test/mock/requirements.txt b/Docker Test/mock/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/Docker Test/mock/server.py b/Docker Test/mock/server.py new file mode 100644 index 0000000..024f1fb --- /dev/null +++ b/Docker Test/mock/server.py @@ -0,0 +1,49 @@ +import json +import signal +import sys +from http.server import BaseHTTPRequestHandler, HTTPServer +from django.http import HttpResponse + + +# Function to generate the response +def send_funct(): + ymlschedule = {"message": False} + to_send = { + "statusCode": 200, + "isBase64ENcoded": "false", + "body": json.dumps(ymlschedule) + } + return json.dumps(to_send) + +# Define request handler +class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + # Send response headers + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + + # Send the response body (the JSON data) + self.wfile.write(send_funct().encode('utf-8')) + +# Graceful shutdown handler +def signal_handler(sig, frame): + print("\nShutting down server gracefully...") + sys.exit(0) + +# Set up and start the server +if __name__ == "__main__": + # Register signal handler for graceful shutdown (e.g., Ctrl+C) + signal.signal(signal.SIGINT, signal_handler) + + # Set the server address (localhost) and port (8080) + server_address = ('', 8080) + httpd = HTTPServer(server_address, RequestHandler) + + print("Server started on port 8080") + try: + # Start the server and listen for requests + httpd.serve_forever() + except KeyboardInterrupt: + # Server shutdown is handled in signal_handler + pass diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f65975b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# Use an official Python runtime as a parent image +FROM python:3.10-slim + +# Set the working directory inside the container +WORKDIR /BajaCloudBackend + +# Copy the current directory contents into the container at /app +COPY ./ContainerContents /BajaCloudBackend + +# Install any necessary dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Expose port 8080 for the container to listen on +EXPOSE 8080 + +# Command to run the Python server when the container starts +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--reload"] + diff --git a/Other Items/AfterMergeCodeapceTestResults.png b/Other Items/AfterMergeCodeapceTestResults.png new file mode 100644 index 0000000..b4c5a58 Binary files /dev/null and b/Other Items/AfterMergeCodeapceTestResults.png differ diff --git a/Other Items/ConversionToExcel.py b/Other Items/ConversionToExcel.py new file mode 100644 index 0000000..2b6e9e9 --- /dev/null +++ b/Other Items/ConversionToExcel.py @@ -0,0 +1,44 @@ +import pandas as pd +import yaml + +# Load the YAML file +with open("./interview_database.yaml", 'r') as file: + interview_data = yaml.safe_load(file) + +# Access the nested structure within 'Data' +flattened_data = [] +for date, date_info in interview_data['Data'].items(): + meeting_duration = date_info.get('Meeting Duration') + + for start_time_info in date_info.get('Meeting Start Times', []): + for start_time, meeting_info in start_time_info.items(): + interviewer_name = meeting_info['Interviewer'][0].get('Name') + interviewer_email = meeting_info['Interviewer'][1].get('Email') + interviewee_name = meeting_info['Interviewee'][0].get('Name') + interviewee_email = meeting_info['Interviewee'][1].get('Email') + category = meeting_info.get('Category') + status = meeting_info.get('Status') + slot = meeting_info.get('Slot') + + # Add flattened row to list + flattened_data.append({ + 'Date': date, + 'Meeting Duration': meeting_duration, + 'Start Time': start_time, + 'Interviewer Name': interviewer_name, + 'Interviewer Email': interviewer_email, + 'Interviewee Name': interviewee_name, + 'Interviewee Email': interviewee_email, + 'Category': category, + 'Status': status, + 'Slot': slot + }) + +# Convert to DataFrame if flattened data is not empty +if flattened_data: + df = pd.DataFrame(flattened_data) + # Write the DataFrame to an Excel file + df.to_excel("interview_database.xlsx", index=False) + print("Data has been written to interview_database.xlsx") +else: + print("No data found to write.") diff --git a/Other Items/FastAPI test 1000 requests.png b/Other Items/FastAPI test 1000 requests.png new file mode 100644 index 0000000..a86b656 Binary files /dev/null and b/Other Items/FastAPI test 1000 requests.png differ diff --git a/Other Items/OR-L-Interview Data.xlsx b/Other Items/OR-L-Interview Data.xlsx new file mode 100644 index 0000000..d44d9a8 Binary files /dev/null and b/Other Items/OR-L-Interview Data.xlsx differ diff --git a/README.md b/Other Items/README.md similarity index 100% rename from README.md rename to Other Items/README.md diff --git a/Other Items/TestBookAppointment.py b/Other Items/TestBookAppointment.py new file mode 100644 index 0000000..f076d51 --- /dev/null +++ b/Other Items/TestBookAppointment.py @@ -0,0 +1,11 @@ +from WriteDB import AppendAppointment + +def TestBookAppointment(): + date_1 = "2024-09-16" + start_time_1 = "10:30:00" + interviewee_name_1 = "Alice Johnson" + interviewee_email_1 = "ahmadmuhammadofficial@gmail.com" + print(f"\nTest Case 1: Trying to book {date_1} at {start_time_1} for {interviewee_name_1} ({interviewee_email_1})") + AppendAppointment(date_1, start_time_1, interviewee_name_1, interviewee_email_1) + +TestBookAppointment() \ No newline at end of file diff --git a/Other Items/TestResultsCodeSpace.png b/Other Items/TestResultsCodeSpace.png new file mode 100644 index 0000000..070d1bc Binary files /dev/null and b/Other Items/TestResultsCodeSpace.png differ diff --git a/Other Items/interview_database.yaml b/Other Items/interview_database.yaml new file mode 100644 index 0000000..9a677ee --- /dev/null +++ b/Other Items/interview_database.yaml @@ -0,0 +1,64 @@ +Data: + September 16: + Meeting Duration: 30 min + Meeting Start Times: + - 10:00 AM: + Interviewer: + - Name: John + - Email: john@example.com + Interviewee: + - Name: Jane + - Email: jane@example.com + Category: PowerDrive + Status: Done + Slot: 2 + - 10:30 AM: + Interviewer: + - Name: Jay + - Email: jay@example.com + Interviewee: + - Name: Zoz + - Email: Zoz@example.com + Category: PowerTrain + Status: Pending + Slot: 3 + - 11:00 AM: + Interviewer: + - Name: Ali + - Email: Ali@example.com + Interviewee: + - Name: Bob + - Email: Bob@example.com + Category: Software + Status: Rescheduled + Slot: 1 + - 11:30 AM: + Interviewer: + - Name: + - Email: + Interviewee: + - Name: + - Email: + Category: + Status: + Slot: 1 + - 12:00 PM: + Interviewer: + - Name: Ali, John + - Email: Ali@example.com, Jhon@example.com + Interviewee: + - Name: Bob + - Email: Bob@example.com + Category: Software + Status: Cancelled + Slot: 1 + - 12:30 PM: + Interviewer: + - Name: Ali + - Email: Ali@example.com + Interviewee: + - Name: Bob, Jish + - Email: Bob@example.com, jish@example.com + Category: Software + Status: Cancelled + Slot: 2 diff --git a/Other Items/testhttp.py b/Other Items/testhttp.py new file mode 100644 index 0000000..d65f6d4 --- /dev/null +++ b/Other Items/testhttp.py @@ -0,0 +1,25 @@ +import json +import requests +import timeit + +def BenchMarkServer(): + rawRes = requests.get("http://localhost:8080/getAppointments") + res = json.loads(rawRes.text) + #print(json.dumps(res, indent=1)) + +def BenchMarkDjango(): + rawRes = requests.get("http://127.0.0.1:8000/getAppointments") + res = json.loads(rawRes.text) + #print(json.dumps(res, indent=1)) + +if __name__ == "__main__": + test = 1 + if test: + djangoTime = timeit.timeit(stmt=BenchMarkDjango, number=1000) + # pythonTime = timeit.timeit(stmt=BenchMarkServer, number=10) + print(f"FastAPI: {djangoTime}\nPython: ") + + reqbody = { + "body": {"message": "hello"} + } + # rawRes = requests.post("http://localhost:8000/SelectInterview", reqbody) diff --git a/Other Items/web1.html b/Other Items/web1.html new file mode 100644 index 0000000..5049285 --- /dev/null +++ b/Other Items/web1.html @@ -0,0 +1,29 @@ + + + Interview Invitation + + +

Dear {interviewee_name},

+

Your interview has been scheduled on {date} at {start_time}

+

Please ensure to be available at the designated time.

+ + + + + + +

Best regards,

+

UCalgary Baja Interview Team

+ UCalgary Baja Team + + diff --git a/ReadDB.py b/ReadDB.py deleted file mode 100644 index 7c7235a..0000000 --- a/ReadDB.py +++ /dev/null @@ -1,42 +0,0 @@ -import pandas as pd -import json -def ReadDatabase(): - """ - Reads the Database - - ``REQUIRES``: None - - ``PROMISES``: JSON (Interview Avaiable Slots) - - ``Develop in part by``: Ahmad - - ``Contact``: ahmad.ahmad1@ucalgary.ca - - """ - # Load the updated Excel file into a pandas DataFrame - excel_file_path = "./interview_database.xlsx" - df = pd.read_excel(excel_file_path) - - # Initialize the dictionary to store the structured data - interview_data = {} - - # Group the DataFrame by Date, Start Time, and Slot for organization - for _, row in df.iterrows(): - date = str(row['Date']) - start_time = str(row['Start Time']) - slot = int(row['Slot']) if not pd.isna(row['Slot']) else 0 - #Returns amount of interviewees in the slot and if it's empty it will return 0 - interviewee_amount = len(str(row['Interviewee Name']).split()) if str(row['Interviewee Name']) != "nan" else 0 - - # Check if the slot is available for an interviewee to attend - avaliable_slots = interviewee_amount != slot - if avaliable_slots: - # Initialize nested structure if not present - if date not in interview_data: - interview_data[date] = {} - #Adds the start time and duration if not present - if start_time not in interview_data[date]: - interview_data[date][start_time] = { - 'Meeting Duration': row['Meeting Duration'], - } - return interview_data diff --git a/composetest/Dockerfile b/composetest/Dockerfile new file mode 100644 index 0000000..8505363 --- /dev/null +++ b/composetest/Dockerfile @@ -0,0 +1,8 @@ +# syntax=docker/dockerfile:1 +FROM python:3.10-slim +WORKDIR /code +COPY requirements.txt requirements.txt +RUN pip install -r requirements.txt +EXPOSE 5000 +COPY . . +CMD ["python", "app.py"] \ No newline at end of file diff --git a/composetest/app.py b/composetest/app.py new file mode 100644 index 0000000..69e5c83 --- /dev/null +++ b/composetest/app.py @@ -0,0 +1,33 @@ +import time +from http.server import BaseHTTPRequestHandler, HTTPServer + +hit_count = 0 # In-memory counter + +def get_hit_count(): + global hit_count + hit_count += 1 + return hit_count + +class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == '/': + count = get_hit_count() + response = f'Hello World! I have been seen {count} times.\n' + self.send_response(200) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(response.encode('utf-8')) + else: + self.send_response(404) + self.send_header('Content-type', 'text/plain') + self.end_headers() + self.wfile.write(b'Not Found\n') + +def run(server_class=HTTPServer, handler_class=RequestHandler, port=8000): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print(f'Starting server on port {port}...') + httpd.serve_forever() + +if __name__ == '__main__': + run() diff --git a/composetest/docker-compose.yaml b/composetest/docker-compose.yaml new file mode 100644 index 0000000..c24417b --- /dev/null +++ b/composetest/docker-compose.yaml @@ -0,0 +1,8 @@ +services: + web: + container_name: test + build: . + ports: + - "8000:8000" + volumes: + - ./:/code diff --git a/composetest/requirements.txt b/composetest/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..f05e221 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,10 @@ +services: + bajacloud: + container_name: bajacloud + ports: + - 8080:8080 + volumes: + - ./mock:/Interviews + build: . + # networks: + # - testnet diff --git a/interview_database.xlsx b/interview_database.xlsx deleted file mode 100644 index 68fc44d..0000000 Binary files a/interview_database.xlsx and /dev/null differ