🪻 distributed transcription service thistle.dunkirk.sh

chore: filter by section id for voting

dunkirk.sh 64e630d1 52c604af

verified
+90 -20
+17 -3
src/components/class-view.ts
··· 637 637 <!-- Pending Recordings for Voting --> 638 638 ${ 639 639 this.meetingTimes.map((meeting) => { 640 - const pendingCount = this.transcriptions.filter( 641 - (t) => t.meeting_time_id === meeting.id && t.status === "pending", 642 - ).length; 640 + // Apply section filtering to pending recordings 641 + const sectionFilter = this.selectedSectionFilter || this.userSection; 642 + 643 + const pendingCount = this.transcriptions.filter((t) => { 644 + if (t.meeting_time_id !== meeting.id || t.status !== "pending") { 645 + return false; 646 + } 647 + 648 + // Filter by section if applicable 649 + if (this.sections.length > 0 && sectionFilter) { 650 + // Show recordings from user's section or no section (unassigned) 651 + return t.section_id === sectionFilter || t.section_id === null; 652 + } 653 + 654 + return true; 655 + }).length; 643 656 644 657 // Only show if there are pending recordings 645 658 if (pendingCount === 0) return ""; ··· 650 663 .classId=${this.classId} 651 664 .meetingTimeId=${meeting.id} 652 665 .meetingTimeLabel=${meeting.label} 666 + .sectionId=${sectionFilter} 653 667 ></pending-recordings-view> 654 668 </div> 655 669 `;
+9 -1
src/components/pending-recordings-view.ts
··· 23 23 @property({ type: String }) classId = ""; 24 24 @property({ type: String }) meetingTimeId = ""; 25 25 @property({ type: String }) meetingTimeLabel = ""; 26 + @property({ type: String }) sectionId: string | null = null; 26 27 27 28 @state() private recordings: PendingRecording[] = []; 28 29 @state() private userVote: string | null = null; ··· 241 242 this.loadingInProgress = true; 242 243 243 244 try { 244 - const response = await fetch( 245 + // Build URL with optional section_id parameter 246 + const url = new URL( 245 247 `/api/classes/${this.classId}/meetings/${this.meetingTimeId}/recordings`, 248 + window.location.origin, 246 249 ); 250 + if (this.sectionId !== null) { 251 + url.searchParams.set("section_id", this.sectionId); 252 + } 253 + 254 + const response = await fetch(url.toString()); 247 255 248 256 if (!response.ok) { 249 257 throw new Error("Failed to load recordings");
+14 -4
src/components/upload-recording-modal.ts
··· 316 316 formData.append("audio", this.selectedFile); 317 317 formData.append("class_id", this.classId); 318 318 319 - // Use user's section by default, or allow override 320 - const sectionToUse = this.selectedSectionId || this.userSection; 321 - if (sectionToUse) { 322 - formData.append("section_id", sectionToUse); 319 + // Send recording date (from date picker or file timestamp) 320 + if (this.selectedDate) { 321 + // Convert YYYY-MM-DD to timestamp (noon local time) 322 + const date = new Date(`${this.selectedDate}T12:00:00`); 323 + formData.append("recording_date", Math.floor(date.getTime() / 1000).toString()); 324 + } else if (this.selectedFile.lastModified) { 325 + // Use file's lastModified as recording date 326 + formData.append("recording_date", Math.floor(this.selectedFile.lastModified / 1000).toString()); 323 327 } 328 + 329 + // Don't send section_id yet - will be set via PATCH when user confirms 324 330 325 331 const xhr = new XMLHttpRequest(); 326 332 ··· 434 440 this.error = null; 435 441 436 442 try { 443 + // Get section to use (selected override or user's section) 444 + const sectionToUse = this.selectedSectionId || this.userSection; 445 + 437 446 const response = await fetch( 438 447 `/api/transcriptions/${this.uploadedTranscriptionId}/meeting-time`, 439 448 { ··· 441 450 headers: { "Content-Type": "application/json" }, 442 451 body: JSON.stringify({ 443 452 meeting_time_id: this.selectedMeetingTimeId, 453 + section_id: sectionToUse, 444 454 }), 445 455 }, 446 456 );
+15
src/db/schema.ts
··· 278 278 CREATE INDEX IF NOT EXISTS idx_recording_votes_user_id ON recording_votes(user_id); 279 279 `, 280 280 }, 281 + { 282 + version: 4, 283 + name: "Add recording_date to transcriptions for chronological ordering", 284 + sql: ` 285 + -- Add recording_date (timestamp when the recording was made, not uploaded) 286 + -- Defaults to created_at for existing records 287 + ALTER TABLE transcriptions ADD COLUMN recording_date INTEGER; 288 + 289 + -- Set recording_date to created_at for existing records 290 + UPDATE transcriptions SET recording_date = created_at WHERE recording_date IS NULL; 291 + 292 + -- Create index for ordering by recording date 293 + CREATE INDEX IF NOT EXISTS idx_transcriptions_recording_date ON transcriptions(recording_date); 294 + `, 295 + }, 281 296 ]; 282 297 283 298 function getCurrentVersion(): number {
+34 -11
src/index.ts
··· 2189 2189 2190 2190 const body = await req.json(); 2191 2191 const meetingTimeId = body.meeting_time_id; 2192 + const sectionId = body.section_id; 2192 2193 2193 2194 if (!meetingTimeId) { 2194 2195 return Response.json( ··· 2235 2236 } 2236 2237 } 2237 2238 2238 - // Update meeting time 2239 - db.run( 2240 - "UPDATE transcriptions SET meeting_time_id = ? WHERE id = ?", 2241 - [meetingTimeId, transcriptionId], 2242 - ); 2239 + // Update meeting time and optionally section_id 2240 + if (sectionId !== undefined) { 2241 + db.run( 2242 + "UPDATE transcriptions SET meeting_time_id = ?, section_id = ? WHERE id = ?", 2243 + [meetingTimeId, sectionId, transcriptionId], 2244 + ); 2245 + } else { 2246 + db.run( 2247 + "UPDATE transcriptions SET meeting_time_id = ? WHERE id = ?", 2248 + [meetingTimeId, transcriptionId], 2249 + ); 2250 + } 2243 2251 2244 2252 return Response.json({ 2245 2253 success: true, ··· 2266 2274 ); 2267 2275 } 2268 2276 2269 - // Get user's section for filtering (admins see all) 2270 - const userSection = 2271 - user.role === "admin" ? null : getUserSection(user.id, classId); 2277 + // Get section filter from query params or use user's section 2278 + const url = new URL(req.url); 2279 + const sectionParam = url.searchParams.get("section_id"); 2280 + const sectionFilter = 2281 + sectionParam !== null 2282 + ? sectionParam || null // empty string becomes null 2283 + : user.role === "admin" 2284 + ? null 2285 + : getUserSection(user.id, classId); 2272 2286 2273 2287 const recordings = getPendingRecordings( 2274 2288 classId, 2275 2289 meetingTimeId, 2276 - userSection, 2290 + sectionFilter, 2277 2291 ); 2278 2292 const totalUsers = getEnrolledUserCount(classId); 2279 2293 const userVote = getUserVoteForMeeting( ··· 2286 2300 const winningId = checkAutoSubmit( 2287 2301 classId, 2288 2302 meetingTimeId, 2289 - userSection, 2303 + sectionFilter, 2290 2304 ); 2291 2305 2292 2306 return Response.json({ ··· 2557 2571 const file = formData.get("audio") as File; 2558 2572 const classId = formData.get("class_id") as string | null; 2559 2573 const sectionId = formData.get("section_id") as string | null; 2574 + const recordingDateStr = formData.get("recording_date") as 2575 + | string 2576 + | null; 2560 2577 2561 2578 if (!file) throw ValidationErrors.missingField("audio"); 2562 2579 ··· 2623 2640 const uploadDir = "./uploads"; 2624 2641 await Bun.write(`${uploadDir}/${filename}`, file); 2625 2642 2643 + // Parse recording date (default to current time if not provided) 2644 + const recordingDate = recordingDateStr 2645 + ? Number.parseInt(recordingDateStr, 10) 2646 + : Math.floor(Date.now() / 1000); 2647 + 2626 2648 // Create database record (without meeting_time_id - will be set later via PATCH) 2627 2649 db.run( 2628 - "INSERT INTO transcriptions (id, user_id, class_id, meeting_time_id, section_id, filename, original_filename, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 2650 + "INSERT INTO transcriptions (id, user_id, class_id, meeting_time_id, section_id, filename, original_filename, status, recording_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", 2629 2651 [ 2630 2652 transcriptionId, 2631 2653 user.id, ··· 2635 2657 filename, 2636 2658 file.name, 2637 2659 "pending", 2660 + recordingDate, 2638 2661 ], 2639 2662 ); 2640 2663
+1 -1
src/lib/classes.ts
··· 406 406 `SELECT id, user_id, meeting_time_id, section_id, filename, original_filename, status, progress, error_message, created_at, updated_at 407 407 FROM transcriptions 408 408 WHERE class_id = ? 409 - ORDER BY created_at DESC`, 409 + ORDER BY recording_date DESC, created_at DESC`, 410 410 ) 411 411 .all(classId); 412 412 }