Skip to content

Commit

Permalink
Merge pull request #39 from p1utoze/local
Browse files Browse the repository at this point in the history
feat: add docstrings to functions and endpoints of modules in app directory
  • Loading branch information
p1utoze authored Dec 3, 2023
2 parents bbd1c37 + 4cccf2d commit 4872ad8
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 9 deletions.
39 changes: 38 additions & 1 deletion app/admin/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,50 @@
from app.admin.utils import db, COOKIE_NAME
from .dependencies import get_templates

# initialize router object with prefix /admin
router = APIRouter(prefix="/admin")


@router.get("/dashboard", tags=["Admin"])
async def admin_dashboard(
request: Request, templates: Jinja2Templates = Depends(get_templates)
):
"""Admin dashboard route, displays all the teams and their members based on
the query params.
Parameters
----------
request: Request object
templates: Jinja2Templates object from dependencies
Returns
-------
:return: HTMLResponse
"""
return templates.TemplateResponse("dashboard.html", {"request": request})


@router.post("/dashboard", response_class=HTMLResponse, tags=["Admin"])
async def dashboard_details(
request: Request,
templates: Jinja2Templates = Depends(get_templates),
track: str = Form(...),
team_id: str = Form(...),
):
"""Dashboard details route, displays all the teams and their members based
on the query params on POST request. The payload is sent to the
dashboard.html template. The template renders the data in a collapsible
table format.
Parameters
----------
request: Request object
templates: Jinja2Templates object from dependencies
team_id: str, team code
Returns
-------
:return: HTMLResponse
"""
member_ids = []
member_names = []
all_timings = []
Expand Down Expand Up @@ -68,6 +95,16 @@ async def dashboard_details(

@router.post("/logout", tags=["Admin"], response_class=RedirectResponse)
async def admin_logout(request: Request):
"""Admin logout route, deletes the cookie and redirects to the home page.
Parameters
----------
request: Request object
Returns
-------
:return: RedirectResponse
"""
response = RedirectResponse(
url=request.url_for("home"),
status_code=status.HTTP_301_MOVED_PERMANENTLY,
Expand Down
6 changes: 6 additions & 0 deletions app/admin/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@


def get_templates():
"""Get the Jinja2Templates object from the 'templates' directory.
Returns
-------
:return: Jinja2Templates object
"""
template_dir = Path(__file__).parent.parent / "templates"
print(template_dir)
return Jinja2Templates(directory=template_dir)
Expand Down
4 changes: 4 additions & 0 deletions app/admin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from json import loads
from firebase_admin import credentials, firestore

# load environment variables
load_dotenv()

GOOGLE_CLIENT_ID = os.environ["GOOGLE_CLIENT_ID"]
Expand All @@ -14,9 +15,12 @@
FIREBASE_CONFIG = loads(os.environ["FIREBASE_CONFIG"])
COOKIE_NAME = os.environ["SESSION_COOKIE_NAME"]

# initialize firebase admin sdk
_cred = credentials.Certificate(FIREBASE_CERT)
_fir_app = firebase_admin.initialize_app(_cred)
# initialize firestore client
db = firestore.client()

# initialize firebase auth
_firebase_app = firebase.initialize_app(FIREBASE_CONFIG)
web_auth = _firebase_app.auth()
39 changes: 37 additions & 2 deletions app/auth/google_sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,62 @@


def get_google_sso() -> GoogleSSO:
"""Get GoogleSSO instance. Required for dependency injection. Parameters
required are GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET. The redirect_uri is
optional (allows dynamic redirect_uri if not provided)
Parameters
----------
None
Returns
-------
:return: GoogleSSO instance
"""
return GoogleSSO(
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
# redirect_uri=f"{HOST}/v1/google/callback", # For development
)


# initialize router object with prefix /v1/google
router = APIRouter(prefix="/v1/google")


@router.get("/login", tags=["Google SSO"])
async def auth_init(request: Request, sso=Depends(get_google_sso)):
"""Initialize auth and redirect."""
"""Initialize auth and redirect. Pass the redirect_uri as parameter to
callback with any host.
Parameters
----------
request: Request object
sso: GoogleSSO instance from dependencies
Returns
-------
:return: SSOBase object
"""
return await sso.get_login_redirect(
redirect_uri=request.url_for("auth_callback")
)


@router.get("/callback", response_class=RedirectResponse, tags=["Google SSO"])
async def auth_callback(request: Request, sso=Depends(get_google_sso)):
"""Verify login."""
"""Verify login. Redirects to home page if successful, else redirects to
root with registration if request is from the QR scan endpoint.
Parameters
----------
request: Request object
sso: GoogleSSO instance from dependencies
Returns
-------
:return: RedirectResponse object
"""
user = await sso.verify_and_process(request)
try:
valid_user = fb_auth.get_user_by_email(user.email)
Expand Down
115 changes: 110 additions & 5 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
from app.participants.register import fetch_user_status, check_participant
from app.settings import data_path, static_dir, template_dir

# Initialize FastAPI app instance with title and version
app = FastAPI(title="Hackme API", version="1.0.0")

# Add CORS middleware to allow all origins to make requests to this API server
allow_all = ["*"]
app.add_middleware(GlobalsMiddleware)
app.add_middleware(
Expand All @@ -28,20 +30,48 @@
allow_headers=allow_all,
)

# Mount static files and routers to the app instance
app.mount("/static", StaticFiles(directory=static_dir), name="static")
app.include_router(google_sso.router)
app.include_router(dashboard.router)

# Initialize Jinja2 templates for rendering HTML pages
templates = Jinja2Templates(directory=template_dir)


@app.on_event("startup")
async def load_data():
"""Load data from CSV file into memory. This function is called when the
app starts up. The data is stored in the global variable `g.df` which is
accessible from any part of the app. This is done to avoid reading the CSV
file from disk every time a request is made to the API server. The global
variable initialization is done in the file `app/api_globals.py` which is
imported at the top of this file.
:param: None :return: None
----------------
"""
g.df = pd.read_csv(data_path)


@app.get("/", response_class=HTMLResponse)
async def home(request: Request, uid: str = None):
async def home(request: Request):
"""Render the home page. This is the first page that is rendered when the
user visits the website. It checks if the user is logged in or not. If the
user is logged in, it redirects to the dashboard page. If the user is not
logged in, it redirects back to the login page based on the session cookie
stored in the browser (created when the user logs in). The cookie name is a
environment variable `COOKIE_NAME` which is defined in the file
`app/admin/utils.py`
Parameters
----------
:param request: FastAPI Request object
Returns
-------
:return: HTMLResponse object
"""
firebase_user_id = request.cookies.get(COOKIE_NAME)
if firebase_user_id:
return templates.TemplateResponse(
Expand All @@ -55,7 +85,27 @@ async def home(request: Request, uid: str = None):
async def email_login(
request: Request, email: str = Form(...), password: str = Form(...)
):
# HANDLE FOR INCORRECT LOGIN CREDENTIAL
"""Authenticate the user using email and password (Password-based
authentication). This function is called when the user submits the login
form on the login page. It uses the Firebase Authentication REST API to
authenticate the user using email stored in the firebase database and the
password entered by the user. It also sets a session if the users are
scanning the QR code for the first time. The session is used to redirect
the user to the register page after successful login. The session is valid
for 30 minutes. The session cookie name is `login` and the user id is
stored in the cookie named `userId`. The cookie name is a environment
variable `COOKIE_NAME` which is defined.
Parameters
----------
request: FastAPI Request object
email: str
password: str
Returns
-------
:return: RedirectResponse object
"""
try:
user = web_auth.sign_in_with_email_and_password(email, password)
if request.cookies.get("login") == "required_by_team":
Expand All @@ -82,6 +132,22 @@ async def email_login(

@app.get("/qr_scan/{uid}", response_class=RedirectResponse)
async def qr_validate(request: Request, uid: str):
"""The endpoint is called when the user scans the QR code. It checks if the
user is logged in or not. If the user is not logged in, it sets a session
cookie and redirects to the register page.
If the user is logged in, it redirects to the register page.
Two cookies are set in the browser - `login` and `userId`, w
which are cleared after successful login and participant registration.
Parameters
----------
request: FastAPI Request object
uid: str
Returns
-------
:return: RedirectResponse object
"""
if not request.cookies.get(COOKIE_NAME):
response = RedirectResponse(url=request.url_for("home"))
response.set_cookie(
Expand All @@ -102,8 +168,27 @@ async def qr_validate(request: Request, uid: str):
"/register", dependencies=[Depends(load_data)], response_class=HTMLResponse
)
async def register_participant(request: Request, uid: str):
"""Render the register page. This page is rendered when the user scans the
QR code for the first time. It checks if the user is logged in or not. If
the user is not logged in, it redirects to the login page. If the user is
logged in, it renders the register page. The user id is the UID of the
participant which is passed as a query parameter. `check_participant`
function returns the status code of the participant to redirect to the
appropriate page.
Parameters
----------
request: FastAPI Request object
uid: str
Returns
-------
:return: HTMLResponse object
"""
member_status_code = check_participant(uid)
if member_status_code == 600:
if (
member_status_code == 600
): # Update status page for already registered participants with timestamp
entry_time = time.strftime(
"%d/%m/%Y %I:%M:%S %p", time.gmtime(time.time() + 19800)
)
Expand All @@ -118,15 +203,19 @@ async def register_participant(request: Request, uid: str):

return response

elif member_status_code == 601:
elif (
member_status_code == 601
): # Fetch details from database and render registration form
details = g.df.loc[g.df["UID"] == uid].to_json(orient="records")
doc = loads(details[1:-1])
doc["status"] = "NULL"
return templates.TemplateResponse(
"master_checkin.html", {"request": request, "Details": doc}
)

elif member_status_code == 602:
elif (
member_status_code == 602
): # Not scanned from original QR code generated by the organizer
return templates.TemplateResponse(
"registration_status.html",
{
Expand All @@ -143,6 +232,22 @@ async def register_participant(request: Request, uid: str):
response_class=HTMLResponse,
)
async def register(request: Request, UID: str):
"""Register the participant. This function is called when the user submits
the register form on the register page. It updates the participant details
in the firebase database and renders the registration status page. The user
id is the UID of the participant which is passed as a query parameter.
Remove the session cookies required if the user hasn't logged in before
scanning the QR code.
Parameters
----------
request
UID
Returns
-------
:return: HTMLResponse object
"""
try:
# print("RESPOND SUCCESS ", g.df.shape)
details = g.df.loc[g.df["UID"] == UID].to_json(orient="records")
Expand Down
Loading

0 comments on commit 4872ad8

Please sign in to comment.