diff --git a/.gitignore b/.gitignore index c6bdbd0..4c49bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -/transcript_42/output/* -!/transcript_42/output/user_local.json .env diff --git a/compose.yaml b/compose.yaml index db98544..3eb6a37 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,8 +6,8 @@ services: dockerfile: Dockerfile ports: - "127.0.0.1:8000:8000" - networks: - - nginx-network + # networks: + # - nginx-network env_file: - .env volumes: @@ -20,4 +20,4 @@ volumes: driver_opts: type: 'none' o: 'bind' - device: './transcript_42/output' \ No newline at end of file + device: '../transcript_42_shared_data' \ No newline at end of file diff --git a/makefile b/makefile index e72a6e0..79ca7f2 100644 --- a/makefile +++ b/makefile @@ -1,15 +1,18 @@ -all: shared-folder docker-up +all: docker-up -docker-up: - docker compose up --build +docker-up: shared-folder + docker compose down + docker compose up --build -d shared-folder: - mkdir -p ./transcript_42/output - chmod -R 777 ./transcript_42/output + mkdir -p ../transcript_42_shared_data + chmod -R 777 ../transcript_42_shared_data -run-local: - $(PYTHON) -m app.local +run-local: shared-folder + docker compose build transcript_42 + docker compose run --rm transcript_42 python3 local.py + echo "The generated files are in ../transcript_42_shared_data" ssl-certif: bash reverse_proxy.sh diff --git a/transcript_42/Dockerfile b/transcript_42/Dockerfile index f8a443c..d58c2c8 100644 --- a/transcript_42/Dockerfile +++ b/transcript_42/Dockerfile @@ -22,4 +22,4 @@ COPY . . WORKDIR /app/app -CMD ["gunicorn", "main:app", "--workers", "1", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--access-logfile", "-", "--access-logformat", "%(t)s %(h)s %(r)s %(s)s %(b)s %(L)ss %(u)s %(q)s", "--error-logfile", "-"] +CMD bash -c "gunicorn main:app --workers 1 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --access-logfile - --error-logfile - >> /app/output/logs.log 2>&1" diff --git a/transcript_42/app/config.py b/transcript_42/app/config.py index f0a1aba..5b64f85 100644 --- a/transcript_42/app/config.py +++ b/transcript_42/app/config.py @@ -1,12 +1,10 @@ import os -from dotenv import load_dotenv -load_dotenv() -UID = os.getenv("42UID") -SECRET = os.getenv("42SECRET") +UID = os.getenv("FORTYTWO_UID") +SECRET = os.getenv("FORTYTWO_SECRET") REDIRECT_URI = os.getenv("OAUTH_REDIRECT_URI", "https://transcript42.project-cloud.cloud/oauth_redirect") AUTH_URL = "https://api.intra.42.fr/oauth/authorize" TOKEN_URL = "https://api.intra.42.fr/oauth/token" USER_URL = "https://api.intra.42.fr/v2/me" LOG_VIEW_PASSWORD = os.getenv("LOG_VIEW_PASSWORD", "changeme") -LOG_PATH = "/var/data/logs.log" +LOG_PATH = "/app/output/logs.log" diff --git a/transcript_42/app/local.py b/transcript_42/app/local.py index 022069b..4824366 100644 --- a/transcript_42/app/local.py +++ b/transcript_42/app/local.py @@ -4,12 +4,12 @@ # provide the "user_local.json" as your user API call response -with open("/app/output/user_local.json", "r", encoding="utf-8") as f: +with open("/app/local_input/local_user.json", "r", encoding="utf-8") as f: user_data = json.load(f) fill_latex_template(user_path = "/app/output/user_local", user_data = user_data, date_of_birth = "place-your-own", - location_of_birth = "plcae-your-own", + location_of_birth = "place-your-own", language = "en", transcript_type = "core_advanced") generate_pdf(user_path = "/app/output/user_local") diff --git a/transcript_42/app/main.py b/transcript_42/app/main.py index cefd9ce..fbd0297 100644 --- a/transcript_42/app/main.py +++ b/transcript_42/app/main.py @@ -13,17 +13,24 @@ import shutil import tempfile +logging.basicConfig( + filename="/app/output/logs.log", + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", +) + app = FastAPI() @app.get("/") def landing_page(): # Construct the OAuth URL for redirect - auth_url = f"{AUTH_URL}?{urllib.parse.urlencode({ + params = { 'client_id': UID, 'redirect_uri': REDIRECT_URI, 'response_type': 'code', 'scope': 'public' - })}" + } + auth_url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}" return render_start_page(auth_url) @@ -43,7 +50,7 @@ def create_transcript( language: str = Form(...), transcript_type: str = Form(...), ): - user_path = os.path.join("/var/data", f"user_{user_id}") + user_path = os.path.join("/app/output", f"user_{user_id}") try: with open(user_path + ".json", "r", encoding="utf-8") as f: user_data = json.load(f) @@ -94,7 +101,7 @@ def get_logs_full(x_api_key: str = Header(...)): if x_api_key != LOG_VIEW_PASSWORD: raise HTTPException(status_code=401, detail="Unauthorized") - log_dir = os.path.dirname("/app/output") # directory containing logs + log_dir = "/app/output" # directory containing logs if not os.path.exists(log_dir): raise HTTPException(status_code=404, detail="Log directory not found") diff --git a/transcript_42/app/services/fill_latex_template.py b/transcript_42/app/services/fill_latex_template.py index 1cde67e..b033c1c 100644 --- a/transcript_42/app/services/fill_latex_template.py +++ b/transcript_42/app/services/fill_latex_template.py @@ -38,6 +38,7 @@ def parse_projects(data: dict, pj_dic: dict, language: str = "en") -> dict: "name": project_data.get("name"), "validated": entry.get("validated?"), "marked_at": marked_at, + "created_at": entry.get("created_at"), "description": description, "grade": entry.get("final_mark"), "hours": project_data.get("hours") @@ -267,7 +268,14 @@ def organize_projects_by_category(parsed: dict) -> dict: def prepare_template_variables(data: dict, parsed: dict, organized: dict, date_of_birth=None, location_of_birth=None, language=None, transcript_type=None) -> dict: """Prepare all variables required for the LaTeX template.""" - school_address = """Harzer Straße 39\n12059 Berlin\nGERMANY""" + school_address = "" + campus_id = None + for campus in data["campus_users"]: + if campus.get("is_primary"): + campus_id = campus.get("campus_id") or 51 + if campus_id == 52: + school_address = """Harzer Straße 39\n12059 Berlin\nGERMANY""" + first_name = data["first_name"].upper() last_name = data["last_name"].upper() # Format date_of_birth if provided @@ -291,15 +299,27 @@ def prepare_template_variables(data: dict, parsed: dict, organized: dict, date_o except Exception as e: passed_selection = f"{month_name} {year}" - target_cursus = next( - (c for c in data["cursus_users"] if c["id"] in {244384, 196717}), - None - ) + # target_cursus = next( + # (c for c in data["cursus_users"] if c["id"] in {244384, 196717}), + # None + # ) + c_rld = parsed.get("C-piscine-reloaded") + libft = parsed.get("Libft") core_started = None - if target_cursus: - raw_date = target_cursus["begin_at"] - dt = datetime.fromisoformat(raw_date.replace("Z", "+00:00")) - core_started = dt.strftime("%d.%m.%Y") + if core_started is None: + dates = [] + for proj in (c_rld, libft): + if proj and proj.get("created_at"): + dt = datetime.fromisoformat(proj["created_at"].replace("Z", "+00:00")) + dates.append(dt) + if dates: + earliest = min(dates) + core_started = earliest.strftime("%d.%m.%Y") + + # if target_cursus: + # raw_date = target_cursus["begin_at"] + # dt = datetime.fromisoformat(raw_date.replace("Z", "+00:00")) + # core_started = dt.strftime("%d.%m.%Y") tran = parsed.get("ft_transcendence") exam = parsed.get("Exam Rank 06") if tran is not None and exam is not None and tran.get("validated") and exam.get("validated"): @@ -321,6 +341,7 @@ def prepare_template_variables(data: dict, parsed: dict, organized: dict, date_o "advanced_completed": advanced_completed, "language": language, "transcript_type": transcript_type, + "campus_id": campus_id } extra_vars.update(organized) return extra_vars diff --git a/transcript_42/app/services/generate_pdf.py b/transcript_42/app/services/generate_pdf.py index 84e0c90..a3fd05e 100644 --- a/transcript_42/app/services/generate_pdf.py +++ b/transcript_42/app/services/generate_pdf.py @@ -2,6 +2,12 @@ import subprocess import logging +logging.basicConfig( + filename="/app/output/logs.log", + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", +) + def generate_pdf(user_path): filled_template_path = user_path + ".tex" diff --git a/transcript_42/app/services/render_input_form.py b/transcript_42/app/services/render_input_form.py index 9019a69..3dc6e76 100644 --- a/transcript_42/app/services/render_input_form.py +++ b/transcript_42/app/services/render_input_form.py @@ -13,11 +13,9 @@ def render_profile_form(user_id: str) -> HTMLResponse: background: #12141a; margin: 0; padding: 0; - height: 100vh; - - /* Flex to center container vertically and horizontally */ + min-height: 100vh; display: flex; - justify-content: center; + flex-direction: column; align-items: center; color: white; }} @@ -38,12 +36,11 @@ def render_profile_form(user_id: str) -> HTMLResponse: color: white; margin: 0; line-height: 1.2; - }} + }} h2 {{ margin-top: 4px; margin-bottom: 30px; }} - form label {{ display: block; margin-bottom: 12px; @@ -82,6 +79,20 @@ def render_profile_form(user_id: str) -> HTMLResponse: button:hover {{ background: #0056b3; }} + .note {{ + max-width: 400px; + color: #cfcfcf; + font-size: 14px; + text-align: center; + margin-bottom: 40px; + }} + .note a {{ + color: #00babc; + text-decoration: none; + }} + .note a:hover {{ + text-decoration: underline; + }} @@ -95,15 +106,15 @@ def render_profile_form(user_id: str) -> HTMLResponse:
@@ -123,6 +134,10 @@ def render_profile_form(user_id: str) -> HTMLResponse: +
+ Only Berlin campus is fully supported so far. If you want to include your campus to have the logo, legal notes, and address featured in the PDF, + let's talk: +