Skip to content

Commit e5daa4a

Browse files
authored
refactor: web cq
improve web app cq
2 parents 33b14e3 + 2b31769 commit e5daa4a

24 files changed

Lines changed: 934 additions & 708 deletions

.env.example

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@ QUEST__LOGIN__API_KEY=dummy2
3030
# QUEST__LOGIN__URL=
3131
# QUEST__LOGIN__QUESTIONID=
3232

33-
QUEST__RESET__API_KEY=dummy3
34-
# QUEST__RESET__URL=
35-
# QUEST__RESET__QUESTIONID=
33+
QUEST__RESET_PASSWORD__API_KEY=dummy3
34+
# QUEST__RESET_PASSWORD__URL=
35+
# QUEST__RESET_PASSWORD__QUESTIONID=
3636

3737
# --- Other Overrides (Optional) ---
3838
# Example of overriding a nested value in the AUTH dictionary
3939
# AUTH__OTP_TIMEOUT=60
40+
# Example of overridng web size constraints
41+
# WEB__COURSE__PAGE_SIZE=5
42+
# WEB__REVIEW__PAGE_SIZE=10
43+
# WEB__REVIEW__COMMENT_MIN_LENGTH=30
4044

4145
# Example of overriding a list with a comma-separated string
4246
# ALLOWED_HOSTS=localhost,127.0.0.1,dev.my-app.com

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ help:
1111
@echo " collect - Collects Django static files"
1212
@echo " install-frontend - Installs frontend dependencies using bun"
1313
@echo " format - Formats both backend (Python) and frontend (JS/TS/CSS) code"
14-
@echo " format-backend - Formats Python code using isort and black"
14+
@echo " format-backend - Formats Python code using ruff check and format"
1515
@echo " format-frontend - Formats frontend code using prettier"
1616
@echo " lint - Lints both backend (Python) and frontend (JS/TS/CSS) code"
1717
@echo " lint-backend - Lints Python code using ruff"
@@ -45,8 +45,8 @@ format: format-backend format-frontend
4545
@echo "All code formatted successfully!"
4646

4747
format-backend:
48-
@echo "Formatting backend (Python) code with isort and black..."
49-
uv run ruff check --select I . && \
48+
@echo "Formatting backend (Python) code with ruff check and format..."
49+
uv run ruff check --select I . --fix && \
5050
uv run ruff format
5151

5252
format-frontend:

apps/auth/urls.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.urls import re_path
2+
3+
from apps.auth import views as auth_views
4+
5+
urlpatterns = [
6+
re_path(r"^init/$", auth_views.auth_initiate_api, name="auth_initiate_api"),
7+
re_path(r"^verify/$", auth_views.verify_callback_api, name="verify_callback_api"),
8+
re_path(
9+
r"^password/$",
10+
auth_views.auth_reset_password_api,
11+
name="auth_reset_password_api",
12+
),
13+
re_path(r"^signup/$", auth_views.auth_signup_api, name="auth_signup_api"),
14+
re_path(r"^login/$", auth_views.auth_login_api, name="auth_login_api"),
15+
re_path(r"^logout/?$", auth_views.auth_logout_api, name="auth_logout_api"),
16+
]

apps/auth/utils.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
from django.contrib.auth.models import AbstractUser
1010
from django.contrib.auth.password_validation import validate_password
1111
from django.core.exceptions import ValidationError
12+
from rest_framework.authentication import SessionAuthentication
1213
from rest_framework.response import Response
1314

1415
from apps.web.models import Student
1516

17+
logger = logging.getLogger(__name__)
18+
1619
AUTH_SETTINGS = settings.AUTH
1720
PASSWORD_LENGTH_MIN = AUTH_SETTINGS["PASSWORD_LENGTH_MIN"]
1821
PASSWORD_LENGTH_MAX = AUTH_SETTINGS["PASSWORD_LENGTH_MAX"]
@@ -23,22 +26,28 @@
2326
QUEST_BASE_URL = QUEST_SETTINGS["BASE_URL"]
2427

2528

29+
class CSRFCheckSessionAuthentication(SessionAuthentication):
30+
def authenticate(self, request):
31+
super().enforce_csrf(request)
32+
return super().authenticate(request)
33+
34+
2635
def get_survey_details(action: str) -> dict[str, Any] | None:
2736
"""
2837
A single, clean function to get all survey details for a given action.
29-
Valid actions: "signup", "login", "reset".
38+
Valid actions: "signup", "login", "reset_password".
3039
"""
3140

3241
action_details = QUEST_SETTINGS.get(action.upper())
3342

3443
if not action_details:
35-
logging.error("Invalid quest action requested: %s", action)
44+
logger.error("Invalid quest action requested: %s", action)
3645
return None
3746

3847
try:
3948
question_id = int(action_details.get("QUESTIONID"))
4049
except (ValueError, TypeError):
41-
logging.error(
50+
logger.error(
4251
"Could not parse 'QUESTIONID' for action '%s'. Check your settings.", action
4352
)
4453
return None
@@ -66,18 +75,18 @@ async def verify_turnstile_token(
6675
},
6776
)
6877
if not response.json().get("success"):
69-
logging.warning("Turnstile verification failed: %s", response.json())
78+
logger.warning("Turnstile verification failed: %s", response.json())
7079
return False, Response(
7180
{"error": "Turnstile verification failed"}, status=403
7281
)
7382
return True, None
7483
except httpx.TimeoutException:
75-
logging.error("Turnstile verification timed out")
84+
logger.error("Turnstile verification timed out")
7685
return False, Response(
7786
{"error": "Turnstile verification timed out"}, status=504
7887
)
79-
except Exception as e:
80-
logging.error(f"Error verifying Turnstile token: {e}")
88+
except Exception:
89+
logger.error("Turnstile verification error")
8190
return False, Response({"error": "Turnstile verification error"}, status=500)
8291

8392

@@ -132,19 +141,19 @@ async def get_latest_answer(
132141
response.raise_for_status() # Raise an exception for bad status codes
133142
full_data = response.json()
134143
except httpx.TimeoutException:
135-
logging.exception("Questionnaire API query timed out")
144+
logger.error("Questionnaire API query timed out")
136145
return None, Response(
137146
{"error": "Questionnaire API query timed out"},
138147
status=504,
139148
)
140-
except httpx.RequestError as e:
141-
logging.exception(f"Error querying questionnaire API: {e}")
149+
except httpx.RequestError:
150+
logger.error("Error querying questionnaire API")
142151
return None, Response(
143152
{"error": "Failed to query questionnaire API"},
144153
status=500,
145154
)
146-
except Exception as e:
147-
logging.exception(f"An unexpected error occurred: {e}")
155+
except Exception:
156+
logger.error("An unexpected error occurred")
148157
return None, Response({"error": "An unexpected error occurred"}, status=500)
149158

150159
# Filter and return only the required fields from the first row
@@ -180,7 +189,7 @@ async def get_latest_answer(
180189
key in filtered_data and filtered_data[key] is not None
181190
for key in ["id", "submitted_at", "account", "otp"]
182191
):
183-
logging.warning("Missing required field(s) in questionnaire response")
192+
logger.warning("Missing required field(s) in questionnaire response")
184193
return None, Response(
185194
{"error": "Missing required field(s) in questionnaire response"},
186195
status=400,
@@ -211,7 +220,8 @@ def rate_password_strength(password: str) -> int:
211220
if re.search(r"[^a-zA-Z0-9\s]", password):
212221
score += 1
213222

214-
length_step = (PASSWORD_LENGTH_MAX - PASSWORD_LENGTH_MIN) // 10
223+
length_range = max(1, PASSWORD_LENGTH_MAX - PASSWORD_LENGTH_MIN)
224+
length_step = max(1, length_range // 10)
215225

216226
score += (len(password) - PASSWORD_LENGTH_MIN) // length_step
217227

0 commit comments

Comments
 (0)