Skip to content

Commit 33de120

Browse files
authored
Merge pull request #36 from Tech-JI/dev
refactor: Improve code quality
2 parents 8012471 + e5daa4a commit 33de120

40 files changed

Lines changed: 1239 additions & 1002 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

.github/workflows/bot.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: feishu bot
2+
3+
on:
4+
branch_protection_rule:
5+
types: [created, deleted]
6+
check_run:
7+
types: [rerequested, completed]
8+
check_suite:
9+
types: [completed]
10+
create:
11+
delete:
12+
deployment_status:
13+
discussion:
14+
types: [created, edited, answered]
15+
discussion_comment:
16+
types: [created, deleted]
17+
fork:
18+
gollum:
19+
issues:
20+
types: [opened, edited, milestoned, pinned, reopened]
21+
issue_comment:
22+
types: [created, deleted]
23+
label:
24+
types: [created, deleted]
25+
merge_group:
26+
types: [checks_requested]
27+
milestone:
28+
types: [opened, deleted]
29+
page_build:
30+
project:
31+
types: [created, deleted, reopened]
32+
project_card:
33+
types: [created, deleted]
34+
project_column:
35+
types: [created, deleted]
36+
public:
37+
pull_request:
38+
branches:
39+
- '*'
40+
types: [opened, reopened]
41+
pull_request_review:
42+
types: [edited, dismissed, submitted]
43+
pull_request_review_comment:
44+
types: [created, edited, deleted]
45+
pull_request_target:
46+
types: [assigned, opened, synchronize, reopened]
47+
push:
48+
branches:
49+
- '*'
50+
registry_package:
51+
types: [published]
52+
release:
53+
types: [published]
54+
55+
jobs:
56+
send-event:
57+
name: Webhook
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: junka/feishu-bot-webhook-action@main
61+
with:
62+
webhook: ${{ secrets.FEISHU_BOT_WEBHOOK }}
63+
signkey: ${{ secrets.FEISHU_BOT_SIGNKEY }}

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,9 @@ cython_debug/
198198
.abstra/
199199

200200
# Visual Studio Code
201-
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
201+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
202202
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
203-
# and can be added to the global gitignore or merged into this file. However, if you prefer,
203+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
204204
# you could uncomment the following to ignore the entire vscode folder
205205
# .vscode/
206206

@@ -217,4 +217,3 @@ __marimo__/
217217

218218
# Streamlit
219219
.streamlit/secrets.toml
220-

.pre-commit-config.yaml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
repos:
2-
- repo: local
2+
3+
- repo: https://github.com/pre-commit/pre-commit-hooks
4+
rev: v6.0.0
35
hooks:
4-
- id: format-and-add
5-
name: Format code and stage changes
6-
entry: uv run scripts/pre-commit-format.py
7-
language: system
8-
pass_filenames: false
6+
- id: trailing-whitespace
7+
- id: end-of-file-fixer
8+
- id: check-yaml
9+
- id: check-added-large-files
10+
11+
- repo: https://github.com/astral-sh/ruff-pre-commit
12+
rev: v0.14.5
13+
hooks:
14+
- id: ruff-check
15+
types_or: [python, pyi]
16+
always_run: true
17+
args: ["--fix"]
18+
- id: ruff-format
19+
types_or: [python, pyi]
920
always_run: true

Makefile

Lines changed: 5 additions & 4 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,9 @@ 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-
uvx ruff format
48+
@echo "Formatting backend (Python) code with ruff check and format..."
49+
uv run ruff check --select I . --fix && \
50+
uv run ruff format
5051

5152
format-frontend:
5253
@echo "Formatting frontend code with prettier..."
@@ -57,7 +58,7 @@ lint: lint-backend lint-frontend
5758

5859
lint-backend: format-backend
5960
@echo "Linting backend (Python) code with ruff..."
60-
uvx ruff check
61+
uv run ruff check
6162

6263
lint-frontend: format-frontend
6364
@echo "Linting frontend code with eslint..."

apps/auth/admin.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

apps/auth/models.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

apps/auth/tests.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

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: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import json
22
import logging
33
import re
4+
from typing import Any
45

56
import httpx
67
from django.conf import settings
78
from django.contrib.auth import get_user_model
89
from django.contrib.auth.models import AbstractUser
910
from django.contrib.auth.password_validation import validate_password
1011
from django.core.exceptions import ValidationError
12+
from rest_framework.authentication import SessionAuthentication
1113
from rest_framework.response import Response
12-
from typing import Any
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)