banao-tech commited on
Commit
3927523
Β·
verified Β·
1 Parent(s): 3cc6090

Create db_utils.py

Browse files
Files changed (1) hide show
  1. db_utils.py +281 -0
db_utils.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import boto3
2
+ import os
3
+ from datetime import datetime
4
+
5
+ # DynamoDB client
6
+ dynamodb = boto3.client("dynamodb", region_name="ap-south-1")
7
+
8
+ # Tables (set via env vars or fallback defaults)
9
+ COURSE_TABLE = os.environ.get("COURSE_TABLE", "CourseTracking")
10
+ SESSION_TABLE = os.environ.get("SESSION_TABLE", "SessionTracking")
11
+
12
+
13
+ def _utc_now():
14
+ """Return ISO 8601 UTC timestamp with Z suffix."""
15
+ return datetime.utcnow().isoformat() + "Z"
16
+
17
+
18
+ def save_course(course_detail: dict, course_name: str):
19
+ """Save a course (only once per course_id)."""
20
+ now = _utc_now()
21
+
22
+ # Enhanced course details with more information
23
+ course_detail_item = {
24
+ "course_id": {"N": str(course_detail["course_id"])},
25
+ "topic_id": {"N": str(course_detail.get("topic_id", 0))},
26
+ "chapter_id": {"N": str(course_detail.get("chapter_id", 0))},
27
+ "total_videos": {"N": str(course_detail.get("total_videos", 0))},
28
+ "programming_language": {"S": course_detail.get("programming_language", "Python")},
29
+ "target_language": {"S": course_detail.get("target_language", "english")},
30
+ "tts_voice": {"S": course_detail.get("tts_voice", "onyx")},
31
+ "tts_gender": {"S": course_detail.get("tts_gender", "male")}
32
+ }
33
+
34
+ dynamodb.put_item(
35
+ TableName=COURSE_TABLE,
36
+ Item={
37
+ "course_id": {"N": str(course_detail["course_id"])},
38
+ "course_name": {"S": course_name},
39
+ "course_detail": {"M": course_detail_item},
40
+ "total_videos": {"N": str(course_detail.get("total_videos", 0))},
41
+ "status": {"S": "active"},
42
+ "created_at": {"S": now},
43
+ "updated_at": {"S": now},
44
+ },
45
+ # Only create if course doesn't exist
46
+ ConditionExpression="attribute_not_exists(course_id)"
47
+ )
48
+ print(f"βœ… Saved course: {course_name} (Course ID: {course_detail['course_id']})")
49
+
50
+
51
+ def save_session(session_id, course_id, topic_id, topic_title, node, status, course_name=None):
52
+ """Save a session record for a specific topic/video."""
53
+ now = _utc_now()
54
+
55
+ item = {
56
+ "session_id": {"S": session_id},
57
+ "course_id": {"N": str(course_id)},
58
+ "topic_id": {"N": str(topic_id)},
59
+ "topic_title": {"S": topic_title},
60
+ "status": {"S": status},
61
+ "node": {"S": node},
62
+ "created_at": {"S": now},
63
+ "updated_at": {"S": now},
64
+ }
65
+
66
+ # Add course name if provided
67
+ if course_name:
68
+ item["course_name"] = {"S": course_name}
69
+
70
+ dynamodb.put_item(TableName=SESSION_TABLE, Item=item)
71
+ print(f"βœ… Saved session {session_id} for course {course_id} - topic {topic_id}")
72
+
73
+
74
+ def update_session(session_id, status=None, node=None, video_url=None):
75
+ """Update session progress (status, node, timestamp, video_url)."""
76
+ now = _utc_now()
77
+
78
+ expr_attr_names = {"#ut": "updated_at"}
79
+ expr_attr_values = {":u": {"S": now}}
80
+ update_expr = ["#ut = :u"]
81
+
82
+ if status:
83
+ expr_attr_names["#st"] = "status"
84
+ expr_attr_values[":s"] = {"S": status}
85
+ update_expr.append("#st = :s")
86
+
87
+ if node:
88
+ expr_attr_names["#nd"] = "node"
89
+ expr_attr_values[":n"] = {"S": node}
90
+ update_expr.append("#nd = :n")
91
+
92
+ if video_url:
93
+ expr_attr_names["#vu"] = "video_url"
94
+ expr_attr_values[":v"] = {"S": video_url}
95
+ update_expr.append("#vu = :v")
96
+
97
+ dynamodb.update_item(
98
+ TableName=SESSION_TABLE,
99
+ Key={"session_id": {"S": session_id}},
100
+ UpdateExpression="SET " + ", ".join(update_expr),
101
+ ExpressionAttributeNames=expr_attr_names,
102
+ ExpressionAttributeValues=expr_attr_values,
103
+ )
104
+ print(f"βœ… Updated session {session_id}: node={node}, status={status}, video_url={video_url}")
105
+
106
+
107
+ def update_course_status(course_id, status, completed_videos=None):
108
+ """Update course status and progress."""
109
+ now = _utc_now()
110
+
111
+ expr_attr_names = {"#ut": "updated_at", "#st": "status"}
112
+ expr_attr_values = {":u": {"S": now}, ":s": {"S": status}}
113
+ update_expr = ["#ut = :u", "#st = :s"]
114
+
115
+ if completed_videos is not None:
116
+ expr_attr_names["#cv"] = "completed_videos"
117
+ expr_attr_values[":c"] = {"N": str(completed_videos)}
118
+ update_expr.append("#cv = :c")
119
+
120
+ dynamodb.update_item(
121
+ TableName=COURSE_TABLE,
122
+ Key={"course_id": {"N": str(course_id)}},
123
+ UpdateExpression="SET " + ", ".join(update_expr),
124
+ ExpressionAttributeNames=expr_attr_names,
125
+ ExpressionAttributeValues=expr_attr_values,
126
+ )
127
+ print(f"βœ… Updated course {course_id}: status={status}, completed_videos={completed_videos}")
128
+
129
+
130
+ # πŸ”§ Enhanced Helpers
131
+ def get_course(course_id: int):
132
+ """Fetch a course record by course_id."""
133
+ response = dynamodb.get_item(
134
+ TableName=COURSE_TABLE,
135
+ Key={"course_id": {"N": str(course_id)}}
136
+ )
137
+ return response.get("Item")
138
+
139
+
140
+ def get_session(session_id: str):
141
+ """Fetch a session record by session_id."""
142
+ response = dynamodb.get_item(
143
+ TableName=SESSION_TABLE,
144
+ Key={"session_id": {"S": session_id}}
145
+ )
146
+ return response.get("Item")
147
+
148
+
149
+ def get_sessions_by_course(course_id: int):
150
+ """Get all sessions for a specific course."""
151
+ response = dynamodb.scan(
152
+ TableName=SESSION_TABLE,
153
+ FilterExpression="course_id = :cid",
154
+ ExpressionAttributeValues={":cid": {"N": str(course_id)}}
155
+ )
156
+ return response.get("Items", [])
157
+
158
+
159
+ def get_course_progress(course_id: int):
160
+ """Get course progress statistics."""
161
+ sessions = get_sessions_by_course(course_id)
162
+
163
+ if not sessions:
164
+ return {"total": 0, "completed": 0, "processing": 0, "failed": 0, "progress_percent": 0}
165
+
166
+ total = len(sessions)
167
+ completed = sum(1 for s in sessions if s.get("status", {}).get("S", "").lower() == "completed")
168
+ processing = sum(1 for s in sessions if s.get("status", {}).get("S", "").lower() in ["processing", "in_progress", "running"])
169
+ failed = sum(1 for s in sessions if s.get("status", {}).get("S", "").lower() in ["failed", "error"])
170
+
171
+ progress_percent = (completed / total) * 100 if total > 0 else 0
172
+
173
+ return {
174
+ "total": total,
175
+ "completed": completed,
176
+ "processing": processing,
177
+ "failed": failed,
178
+ "progress_percent": round(progress_percent, 1)
179
+ }
180
+
181
+
182
+ def list_all_courses():
183
+ """List all courses with their basic info."""
184
+ response = dynamodb.scan(TableName=COURSE_TABLE)
185
+ return response.get("Items", [])
186
+
187
+
188
+ def list_recent_sessions(limit=10):
189
+ """List recent sessions across all courses."""
190
+ response = dynamodb.scan(TableName=SESSION_TABLE)
191
+ sessions = response.get("Items", [])
192
+
193
+ # Sort by updated_at descending
194
+ sessions.sort(key=lambda x: x.get("updated_at", {}).get("S", ""), reverse=True)
195
+
196
+ return sessions[:limit]
197
+
198
+
199
+ def delete_course(course_id: int):
200
+ """Delete a course and all its associated sessions."""
201
+ try:
202
+ # Delete all sessions for this course
203
+ sessions = get_sessions_by_course(course_id)
204
+ for session in sessions:
205
+ session_id = session.get("session_id", {}).get("S")
206
+ if session_id:
207
+ dynamodb.delete_item(
208
+ TableName=SESSION_TABLE,
209
+ Key={"session_id": {"S": session_id}}
210
+ )
211
+
212
+ # Delete the course
213
+ dynamodb.delete_item(
214
+ TableName=COURSE_TABLE,
215
+ Key={"course_id": {"N": str(course_id)}}
216
+ )
217
+
218
+ print(f"βœ… Deleted course {course_id} and {len(sessions)} associated sessions")
219
+ return True
220
+ except Exception as e:
221
+ print(f"❌ Error deleting course {course_id}: {e}")
222
+ return False
223
+
224
+
225
+ def _dict_to_dynamodb(data):
226
+ """Recursively convert dict/list into DynamoDB structure."""
227
+ if isinstance(data, dict):
228
+ return {"M": {k: _dict_to_dynamodb(v) for k, v in data.items()}}
229
+ elif isinstance(data, list):
230
+ return {"L": [_dict_to_dynamodb(v) for v in data]}
231
+ elif isinstance(data, str):
232
+ return {"S": data}
233
+ elif isinstance(data, bool):
234
+ return {"BOOL": data}
235
+ elif isinstance(data, (int, float)):
236
+ return {"N": str(data)}
237
+ elif data is None:
238
+ return {"NULL": True}
239
+ else:
240
+ raise TypeError(f"Unsupported type: {type(data)}")
241
+
242
+
243
+ # Course name mapping helper
244
+ COURSE_ID_TO_NAME = {
245
+ 47: "AI Ready Course",
246
+ 48: "MERN",
247
+ 49: "Machine Learning",
248
+ 50: "Data Science",
249
+ 51: "Flask",
250
+ 52: "Django"
251
+ }
252
+
253
+ def get_course_name_by_id(course_id):
254
+ """Get course name by course ID."""
255
+ return COURSE_ID_TO_NAME.get(int(course_id), f"Course {course_id}")
256
+
257
+
258
+ # Batch operations
259
+ def save_course_with_sessions(course_detail, course_name, session_data_list):
260
+ """Save course and multiple sessions in sequence."""
261
+ try:
262
+ # Save course first
263
+ save_course(course_detail, course_name)
264
+
265
+ # Save all sessions
266
+ for session_data in session_data_list:
267
+ save_session(
268
+ session_data["session_id"],
269
+ session_data["course_id"],
270
+ session_data["topic_id"],
271
+ session_data["topic_title"],
272
+ session_data.get("node", "initialization"),
273
+ session_data.get("status", "created"),
274
+ course_name
275
+ )
276
+
277
+ print(f"βœ… Saved course {course_name} with {len(session_data_list)} sessions")
278
+ return True
279
+ except Exception as e:
280
+ print(f"❌ Error saving course with sessions: {e}")
281
+ return False