2023-08-30 12:24:25 +02:00
|
|
|
import tomllib
|
|
|
|
from datetime import datetime
|
|
|
|
from pathlib import Path
|
|
|
|
import json
|
|
|
|
import enum
|
|
|
|
from pydantic import BaseModel, SecretStr, constr
|
2023-08-28 10:27:55 +02:00
|
|
|
import secrets
|
|
|
|
from fastapi import FastAPI, HTTPException, Security
|
|
|
|
from fastapi.openapi.models import APIKey
|
2023-08-30 12:24:25 +02:00
|
|
|
from fastapi.security.api_key import APIKeyHeader
|
|
|
|
from starlette.status import HTTP_403_FORBIDDEN
|
|
|
|
import sys
|
|
|
|
|
|
|
|
class Settings(BaseModel):
|
|
|
|
api_key: SecretStr
|
|
|
|
path_support_reports: Path
|
|
|
|
|
|
|
|
with open(Path(__file__).parent / 'settings.toml', 'r') as f:
|
|
|
|
settings_dict = tomllib.loads(f.read())
|
|
|
|
SETTINGS = Settings(**settings_dict)
|
2023-08-28 10:27:55 +02:00
|
|
|
|
|
|
|
####################
|
|
|
|
# - Utilities
|
|
|
|
####################
|
2023-08-30 12:24:25 +02:00
|
|
|
api_key_header = APIKeyHeader(
|
|
|
|
name="access_token",
|
|
|
|
auto_error=False,
|
|
|
|
)
|
|
|
|
|
2023-08-28 10:27:55 +02:00
|
|
|
async def g_api_key(
|
|
|
|
api_key_header: APIKey = Security(api_key_header),
|
|
|
|
) -> APIKey:
|
|
|
|
if api_key_header and secrets.compare_digest(
|
|
|
|
str(api_key_header),
|
2023-08-30 12:24:25 +02:00
|
|
|
SETTINGS.api_key.get_secret_value(),
|
2023-08-28 10:27:55 +02:00
|
|
|
):
|
|
|
|
return api_key_header
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=HTTP_403_FORBIDDEN,
|
|
|
|
detail="Wrong API key.",
|
|
|
|
)
|
|
|
|
|
|
|
|
####################
|
|
|
|
# - Types
|
|
|
|
####################
|
2023-08-30 12:24:25 +02:00
|
|
|
class PyInstSupTag(enum.StrEnum):
|
|
|
|
INST_WIN_OFFICIAL = enum.auto()
|
|
|
|
INST_WIN_WINSTORE = enum.auto()
|
|
|
|
INST_MAC_OFFICIAL = enum.auto()
|
|
|
|
INST_MAC_HOMEBREW = enum.auto()
|
|
|
|
INST_LIN_OFFICIAL = enum.auto()
|
|
|
|
INST_LIN_PKG = enum.auto()
|
|
|
|
COURSE_MATH1A = enum.auto()
|
|
|
|
COURSE_INTROPROG = enum.auto()
|
|
|
|
ENV_PIP = enum.auto()
|
|
|
|
ENV_CONDA = enum.auto()
|
|
|
|
IDE_VSCODE = enum.auto()
|
|
|
|
IDE_PYCHARM = enum.auto()
|
|
|
|
IDE_SPYDER = enum.auto()
|
|
|
|
IDE_NOTEPADPP = enum.auto()
|
|
|
|
|
|
|
|
class ClockOption(enum.IntEnum):
|
|
|
|
CLOCK_IN = enum.auto()
|
|
|
|
CLOCK_OUT = enum.auto()
|
|
|
|
|
|
|
|
class AttendanceEntry(BaseModel):
|
|
|
|
at: datetime
|
|
|
|
status: ClockOption
|
|
|
|
|
|
|
|
class SupportEntry(BaseModel):
|
|
|
|
study_id: constr(pattern = r'^s[0-9]{6}$')
|
|
|
|
tags: list[PyInstSupTag]
|
|
|
|
description: str
|
2023-08-28 10:27:55 +02:00
|
|
|
|
|
|
|
####################
|
|
|
|
# - FastAPI App
|
|
|
|
####################
|
|
|
|
app = FastAPI(
|
2023-08-30 12:24:25 +02:00
|
|
|
#prefix="/v1",
|
2023-08-28 10:27:55 +02:00
|
|
|
dependencies=[Security(g_api_key)],
|
|
|
|
)
|
|
|
|
|
2023-08-30 12:24:25 +02:00
|
|
|
####################
|
|
|
|
# - Support
|
|
|
|
####################
|
|
|
|
@app.get("/report/support")
|
|
|
|
async def g_support_entry() -> list[SupportEntry]:
|
|
|
|
"""Report an instance of granted Python Installation support."""
|
|
|
|
|
|
|
|
with open(SETTINGS.path_support_reports, 'r') as f:
|
|
|
|
return [
|
|
|
|
SupportEntry(support_entry_snippet)
|
|
|
|
for support_entry_snippet in f.readlines()
|
|
|
|
]
|
|
|
|
|
2023-08-28 10:27:55 +02:00
|
|
|
@app.post("/report/support")
|
2023-08-30 12:24:25 +02:00
|
|
|
async def mk_support_entry(
|
|
|
|
support_entry: SupportEntry,
|
2023-08-28 10:27:55 +02:00
|
|
|
) -> bool:
|
2023-08-30 12:24:25 +02:00
|
|
|
"""Print granted Python Support."""
|
|
|
|
|
|
|
|
with open(SETTINGS.path_support_reports, 'a') as f:
|
|
|
|
print(
|
|
|
|
json.dumps(support_entry.model_dump()),
|
|
|
|
file = f,
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
####################
|
|
|
|
# - Hours
|
|
|
|
####################
|
|
|
|
@app.get("/report/hours")
|
|
|
|
async def g_hours_entry() -> list[SupportEntry]:
|
|
|
|
"""Print support attendance record."""
|
|
|
|
|
|
|
|
with open(SETTINGS.path_support_reports, 'r') as f:
|
|
|
|
return [
|
|
|
|
SupportEntry(support_entry_snippet)
|
|
|
|
for support_entry_snippet in f.readlines()
|
|
|
|
]
|
|
|
|
|
|
|
|
@app.post("/report/hours")
|
|
|
|
async def mk_hours_entry(
|
|
|
|
attendance_entry: AttendanceEntry,
|
|
|
|
) -> bool:
|
|
|
|
"""Clock in / out to Python support."""
|
|
|
|
|
|
|
|
with open(SETTINGS.path_support_reports, 'a') as f:
|
|
|
|
print(
|
|
|
|
json.dumps(attendance_entry.model_dump()),
|
|
|
|
file = f,
|
|
|
|
)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|