github-actions[bot] commited on
Commit
8d26ce6
·
1 Parent(s): 2f5bcd1

Auto-deploy backend from GitHub Actions

Browse files
app/api/v1/endpoints/pathways.py CHANGED
@@ -6,6 +6,7 @@ from app.schemas.pathways import (
6
  PathwayCatalogResponse,
7
  PathwaySchema,
8
  PathwayTaskSchema,
 
9
  )
10
 
11
  logger = logging.getLogger(__name__)
@@ -52,7 +53,9 @@ async def get_pathway_catalog(
52
 
53
  for p in catalog_res.data:
54
  p_id = p["id"]
55
- u_state = user_enrollments.get(p_id, {"status": "available", "tasks": {}})
 
 
56
 
57
  status_val = u_state["status"]
58
 
@@ -82,10 +85,10 @@ async def get_pathway_catalog(
82
  int((completed_tasks / task_count * 100)) if task_count > 0 else 0
83
  )
84
 
85
- if status_val == "active":
86
  active_count += 1
87
  total_progress_sum += progress_pct
88
- elif status_val == "completed":
89
  total_points_earned += p.get("total_points", 0)
90
 
91
  pathways_list.append(
@@ -125,49 +128,34 @@ async def enroll_in_pathway(
125
  ):
126
  db_client, user_id = db_data
127
  try:
128
- # 1. Ensure pathway exists and user isn't already enrolled
129
- existing = (
130
- db_client.table("user_pathways")
131
- .select("id")
132
- .eq("user_id", user_id)
133
- .eq("pathway_id", pathway_id)
134
- .execute()
135
- )
136
- if existing.data:
137
- raise HTTPException(
138
- status_code=400,
139
- detail="User is already enrolled or has abandoned this pathway.",
140
- )
141
-
142
- # 2. Create Enrollment
143
- enroll_res = (
144
- db_client.table("user_pathways")
145
- .insert({"user_id": user_id, "pathway_id": pathway_id, "status": "active"})
146
- .execute()
147
- )
148
 
149
- user_pathway_id = enroll_res.data[0]["id"]
150
 
151
- # 3. Fetch all tasks for this pathway
152
- tasks_res = (
153
- db_client.table("pathway_tasks")
154
- .select("id")
155
- .eq("pathway_id", pathway_id)
156
- .execute()
157
  )
158
 
159
- # 4. Create empty progress rows for each task so the tracker works immediately
160
- task_inserts = [
161
- {"user_id": user_id, "task_id": t["id"], "user_pathway_id": user_pathway_id}
162
- for t in tasks_res.data
163
- ]
164
-
165
- if task_inserts:
166
- db_client.table("user_pathway_tasks").insert(task_inserts).execute()
 
 
 
 
167
 
168
- return {"status": "success", "message": "Successfully enrolled in Pathway!"}
169
- except HTTPException:
170
- raise
171
- except Exception as e:
172
- logger.error(f"Failed to enroll in pathway {pathway_id}: {e}", exc_info=True)
173
- raise HTTPException(status_code=500, detail="Enrollment failed.")
 
6
  PathwayCatalogResponse,
7
  PathwaySchema,
8
  PathwayTaskSchema,
9
+ PathwayStatus,
10
  )
11
 
12
  logger = logging.getLogger(__name__)
 
53
 
54
  for p in catalog_res.data:
55
  p_id = p["id"]
56
+ u_state = user_enrollments.get(
57
+ p_id, {"status": PathwayStatus.AVAILABLE.value, "tasks": {}}
58
+ )
59
 
60
  status_val = u_state["status"]
61
 
 
85
  int((completed_tasks / task_count * 100)) if task_count > 0 else 0
86
  )
87
 
88
+ if status_val == PathwayStatus.ACTIVE.value:
89
  active_count += 1
90
  total_progress_sum += progress_pct
91
+ elif status_val == PathwayStatus.COMPLETED.value:
92
  total_points_earned += p.get("total_points", 0)
93
 
94
  pathways_list.append(
 
128
  ):
129
  db_client, user_id = db_data
130
  try:
131
+ # Atomic Transaction execution via our Supabase RPC
132
+ # This replaces 4 brittle REST calls with 1 perfectly safe transaction.
133
+ res = db_client.rpc(
134
+ "enroll_user_in_pathway", {"p_user_id": user_id, "p_pathway_id": pathway_id}
135
+ ).execute()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ return res.data
138
 
139
+ except Exception as e:
140
+ error_message = str(e)
141
+ logger.error(
142
+ f"Failed to enroll in pathway {pathway_id}: {error_message}", exc_info=True
 
 
143
  )
144
 
145
+ # Handle the specific exceptions raised from our PL/pgSQL function
146
+ if (
147
+ "already actively enrolled" in error_message
148
+ or "already completed" in error_message
149
+ or "attempted or abandoned" in error_message
150
+ ):
151
+ raise HTTPException(
152
+ status_code=status.HTTP_400_BAD_REQUEST,
153
+ detail=error_message.split("P0001: ")[
154
+ -1
155
+ ], # Extract just the message if wrapped by Postgres
156
+ )
157
 
158
+ raise HTTPException(
159
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
160
+ detail="Enrollment failed due to server error.",
161
+ )
 
 
app/schemas/pathways.py CHANGED
@@ -1,6 +1,28 @@
 
1
  from pydantic import BaseModel, Field
2
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  class PathwayTaskSchema(BaseModel):
5
  id: str
6
  description: str = Field(description="The visible task instruction for the user")
@@ -12,10 +34,10 @@ class PathwaySchema(BaseModel):
12
  title: str
13
  description: str
14
  image_url: str
15
- difficulty: str
16
  total_points: int
17
- target_strand: str
18
- status: str = Field(description="'available', 'active', or 'completed'")
19
  progress_percentage: int = Field(default=0)
20
  tasks: list[PathwayTaskSchema] = []
21
 
 
1
+ from enum import Enum
2
  from pydantic import BaseModel, Field
3
 
4
 
5
+ class PathwayStatus(str, Enum):
6
+ AVAILABLE = "available"
7
+ ACTIVE = "active"
8
+ COMPLETED = "completed"
9
+ ABANDONED = "abandoned"
10
+
11
+
12
+ class PathwayDifficulty(str, Enum):
13
+ BEGINNER = "Beginner"
14
+ INTERMEDIATE = "Intermediate"
15
+ ADVANCED = "Advanced"
16
+
17
+
18
+ class PathwayStrand(str, Enum):
19
+ STEM = "STEM"
20
+ HUMSS = "HUMSS"
21
+ ABM = "ABM"
22
+ TVL = "TVL"
23
+ GENERAL = "GENERAL"
24
+
25
+
26
  class PathwayTaskSchema(BaseModel):
27
  id: str
28
  description: str = Field(description="The visible task instruction for the user")
 
34
  title: str
35
  description: str
36
  image_url: str
37
+ difficulty: PathwayDifficulty
38
  total_points: int
39
+ target_strand: PathwayStrand
40
+ status: PathwayStatus = Field(description="User's current status for this pathway")
41
  progress_percentage: int = Field(default=0)
42
  tasks: list[PathwayTaskSchema] = []
43