Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/static_analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Static Code Analysis

on: pull_request

jobs:
pylint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v3
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actions/setup-python@v3 action is outdated. Consider upgrading to actions/setup-python@v5 for better performance and security updates.

Suggested change
uses: actions/setup-python@v3
uses: actions/setup-python@v5

Copilot uses AI. Check for mistakes.
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install -r css-reports/requirements.txt
pip install pylint
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')

mypy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v3
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actions/setup-python@v3 action is outdated. Consider upgrading to actions/setup-python@v5 for better performance and security updates.

Suggested change
uses: actions/setup-python@v3
uses: actions/setup-python@v5

Copilot uses AI. Check for mistakes.
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install mypy
pip install -r css-reports/requirements.txt
mypy --install-types
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running 'mypy --install-types' without the '--non-interactive' flag may cause the workflow to hang waiting for user input. Consider adding '--non-interactive' flag: 'mypy --install-types --non-interactive'.

Suggested change
mypy --install-types
mypy --install-types --non-interactive

Copilot uses AI. Check for mistakes.

- name: Analysing the code with mypy
run: mypy .

ruff:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v3
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actions/setup-python@v3 action is outdated. Consider upgrading to actions/setup-python@v5 for better performance and security updates.

Suggested change
uses: actions/setup-python@v3
uses: actions/setup-python@v5

Copilot uses AI. Check for mistakes.
with:
python-version: "3.12"
- name: Install dependencies
run: |
pip install -r css-reports/requirements.txt
pip install ruff
- name: Analysing the code with ruff
run: ruff check --no-fix --output-format=github
31 changes: 22 additions & 9 deletions css-reports/app.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
from flask import Flask, request, send_file, jsonify, redirect
from report import get_product_customisations
"""Module to handle the incoming http requests and generate reports."""

from datetime import datetime
import os
import re

from flask import Flask, request, send_file, jsonify, redirect
from report import get_product_customisations


app = Flask("css-reports")


@app.route("/")
def hello():
"""Redirect to the main website if no specific route is requested."""
return redirect("https://cssbham.com", code=302)


@app.errorhandler(404)
def page_not_found(e: Exception | int):
print(e)
def page_not_found(exception: Exception | int):
"""Handle 404 errors by redirecting to the main website."""
print(exception)
return redirect("https://cssbham.com", code=302)


@app.route("/customisation_report", methods=["GET"])
async def fetch_customisation_report():
"""Fetch the report based on query parameters."""
# Retrieve query parameters
auth_cookie: str | None = request.args.get("auth_cookie")
organisation_id: str | None = request.args.get("organisation_id")
Expand All @@ -33,13 +40,19 @@ async def fetch_customisation_report():
print(f"Product Name: {product_name}")

if not auth_cookie or not organisation_id:
return jsonify({"error": "An auth token and organisation id are required."}), 400
return jsonify(
{"error": "An auth token and organisation id are required."}
), 400

if (not product_name) and (not product_names):
return jsonify({"error": "Either product_name or product_names is required."}), 400
return jsonify(
{"error": "Either product_name or product_names is required."}
), 400

if product_name and product_names:
return jsonify({"error": "Both product_name and product_names cannot be provided."}), 400
return jsonify(
{"error": "Both product_name and product_names cannot be provided."}
), 400

if not report_type:
report_type = "Customisations"
Expand Down Expand Up @@ -80,8 +93,8 @@ async def fetch_customisation_report():

# Return the file as a response
return send_file(csv_file_path, as_attachment=True)
except Exception as e:
return jsonify({"error": str(e)}), 500
except Exception as unknown_error:
return jsonify({"error": str(unknown_error)}), 500
finally:
# Clean up the generated file
if csv_file_path and os.path.exists(csv_file_path):
Expand Down
44 changes: 32 additions & 12 deletions css-reports/report.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Python script to fetch a customisation report from the Guild website."""

import re
from datetime import datetime

from typing import Final, Mapping, TYPE_CHECKING
import aiohttp
import bs4
from bs4 import BeautifulSoup
import re
from datetime import datetime


if TYPE_CHECKING:
from http.cookies import Morsel
Expand All @@ -16,13 +18,23 @@
"Expires": "0",
}

SALES_FROM_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtFromDate"
SALES_FROM_TIME_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtFromTime"
SALES_TO_DATE_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToDate"
SALES_TO_TIME_KEY: Final[str] = "ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToTime"


async def get_msl_context(url: str, auth_cookie: str) -> tuple[dict[str, str], dict[str, str]]:
SALES_FROM_DATE_KEY: Final[str] = (
"ctl00$ctl00$Main$AdminPageContent$drDateRange$txtFromDate"
)
SALES_FROM_TIME_KEY: Final[str] = (
"ctl00$ctl00$Main$AdminPageContent$drDateRange$txtFromTime"
)
SALES_TO_DATE_KEY: Final[str] = (
"ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToDate"
)
SALES_TO_TIME_KEY: Final[str] = (
"ctl00$ctl00$Main$AdminPageContent$drDateRange$txtToTime"
)


async def get_msl_context(
url: str, auth_cookie: str
) -> tuple[dict[str, str], dict[str, str]]:
"""Get the required context headers, data and cookies to make a request to MSL."""

BASE_COOKIES: Mapping[str, str] = {
Expand Down Expand Up @@ -70,8 +82,13 @@ async def fetch_report_url_and_cookies(
report_type: str,
) -> tuple[str | None, dict[str, str]]:
"""Fetch the specified report from the guild website."""
SALES_REPORTS_URL: Final[str] = f"https://www.guildofstudents.com/organisation/salesreports/{org_id}/"
data_fields, cookies = await get_msl_context(url=SALES_REPORTS_URL, auth_cookie=auth_cookie)
SALES_REPORTS_URL: Final[str] = (
f"https://www.guildofstudents.com/organisation/salesreports/{org_id}/"
)

data_fields, cookies = await get_msl_context(
url=SALES_REPORTS_URL, auth_cookie=auth_cookie
)

form_data: dict[str, str] = {
SALES_FROM_DATE_KEY: from_date.strftime("%d/%m/%Y"),
Expand All @@ -91,7 +108,10 @@ async def fetch_report_url_and_cookies(
headers=BASE_HEADERS,
cookies=cookies,
)
async with (session_v2, session_v2.post(url=SALES_REPORTS_URL, data=data_fields) as http_response): # noqa: E501
async with (
session_v2,
session_v2.post(url=SALES_REPORTS_URL, data=data_fields) as http_response,
):
if http_response.status != 200:
print("Returned a non 200 status code!!")
print(http_response)
Expand Down
Loading