🪻 distributed transcription service thistle.dunkirk.sh

chore: filter by section id for voting

dunkirk.sh 64e630d1 52c604af

verified
+17 -3
src/components/class-view.ts
···
<!-- Pending Recordings for Voting -->
${
this.meetingTimes.map((meeting) => {
-
const pendingCount = this.transcriptions.filter(
-
(t) => t.meeting_time_id === meeting.id && t.status === "pending",
-
).length;
+
// Apply section filtering to pending recordings
+
const sectionFilter = this.selectedSectionFilter || this.userSection;
+
+
const pendingCount = this.transcriptions.filter((t) => {
+
if (t.meeting_time_id !== meeting.id || t.status !== "pending") {
+
return false;
+
}
+
+
// Filter by section if applicable
+
if (this.sections.length > 0 && sectionFilter) {
+
// Show recordings from user's section or no section (unassigned)
+
return t.section_id === sectionFilter || t.section_id === null;
+
}
+
+
return true;
+
}).length;
// Only show if there are pending recordings
if (pendingCount === 0) return "";
···
.classId=${this.classId}
.meetingTimeId=${meeting.id}
.meetingTimeLabel=${meeting.label}
+
.sectionId=${sectionFilter}
></pending-recordings-view>
</div>
`;
+9 -1
src/components/pending-recordings-view.ts
···
@property({ type: String }) classId = "";
@property({ type: String }) meetingTimeId = "";
@property({ type: String }) meetingTimeLabel = "";
+
@property({ type: String }) sectionId: string | null = null;
@state() private recordings: PendingRecording[] = [];
@state() private userVote: string | null = null;
···
this.loadingInProgress = true;
try {
-
const response = await fetch(
+
// Build URL with optional section_id parameter
+
const url = new URL(
`/api/classes/${this.classId}/meetings/${this.meetingTimeId}/recordings`,
+
window.location.origin,
);
+
if (this.sectionId !== null) {
+
url.searchParams.set("section_id", this.sectionId);
+
}
+
+
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error("Failed to load recordings");
+14 -4
src/components/upload-recording-modal.ts
···
formData.append("audio", this.selectedFile);
formData.append("class_id", this.classId);
-
// Use user's section by default, or allow override
-
const sectionToUse = this.selectedSectionId || this.userSection;
-
if (sectionToUse) {
-
formData.append("section_id", sectionToUse);
+
// Send recording date (from date picker or file timestamp)
+
if (this.selectedDate) {
+
// Convert YYYY-MM-DD to timestamp (noon local time)
+
const date = new Date(`${this.selectedDate}T12:00:00`);
+
formData.append("recording_date", Math.floor(date.getTime() / 1000).toString());
+
} else if (this.selectedFile.lastModified) {
+
// Use file's lastModified as recording date
+
formData.append("recording_date", Math.floor(this.selectedFile.lastModified / 1000).toString());
}
+
+
// Don't send section_id yet - will be set via PATCH when user confirms
const xhr = new XMLHttpRequest();
···
this.error = null;
try {
+
// Get section to use (selected override or user's section)
+
const sectionToUse = this.selectedSectionId || this.userSection;
+
const response = await fetch(
`/api/transcriptions/${this.uploadedTranscriptionId}/meeting-time`,
{
···
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
meeting_time_id: this.selectedMeetingTimeId,
+
section_id: sectionToUse,
}),
},
);
+15
src/db/schema.ts
···
CREATE INDEX IF NOT EXISTS idx_recording_votes_user_id ON recording_votes(user_id);
`,
},
+
{
+
version: 4,
+
name: "Add recording_date to transcriptions for chronological ordering",
+
sql: `
+
-- Add recording_date (timestamp when the recording was made, not uploaded)
+
-- Defaults to created_at for existing records
+
ALTER TABLE transcriptions ADD COLUMN recording_date INTEGER;
+
+
-- Set recording_date to created_at for existing records
+
UPDATE transcriptions SET recording_date = created_at WHERE recording_date IS NULL;
+
+
-- Create index for ordering by recording date
+
CREATE INDEX IF NOT EXISTS idx_transcriptions_recording_date ON transcriptions(recording_date);
+
`,
+
},
];
function getCurrentVersion(): number {
+34 -11
src/index.ts
···
const body = await req.json();
const meetingTimeId = body.meeting_time_id;
+
const sectionId = body.section_id;
if (!meetingTimeId) {
return Response.json(
···
-
// Update meeting time
-
db.run(
-
"UPDATE transcriptions SET meeting_time_id = ? WHERE id = ?",
-
[meetingTimeId, transcriptionId],
-
);
+
// Update meeting time and optionally section_id
+
if (sectionId !== undefined) {
+
db.run(
+
"UPDATE transcriptions SET meeting_time_id = ?, section_id = ? WHERE id = ?",
+
[meetingTimeId, sectionId, transcriptionId],
+
);
+
} else {
+
db.run(
+
"UPDATE transcriptions SET meeting_time_id = ? WHERE id = ?",
+
[meetingTimeId, transcriptionId],
+
);
+
}
return Response.json({
success: true,
···
);
-
// Get user's section for filtering (admins see all)
-
const userSection =
-
user.role === "admin" ? null : getUserSection(user.id, classId);
+
// Get section filter from query params or use user's section
+
const url = new URL(req.url);
+
const sectionParam = url.searchParams.get("section_id");
+
const sectionFilter =
+
sectionParam !== null
+
? sectionParam || null // empty string becomes null
+
: user.role === "admin"
+
? null
+
: getUserSection(user.id, classId);
const recordings = getPendingRecordings(
classId,
meetingTimeId,
-
userSection,
+
sectionFilter,
);
const totalUsers = getEnrolledUserCount(classId);
const userVote = getUserVoteForMeeting(
···
const winningId = checkAutoSubmit(
classId,
meetingTimeId,
-
userSection,
+
sectionFilter,
);
return Response.json({
···
const file = formData.get("audio") as File;
const classId = formData.get("class_id") as string | null;
const sectionId = formData.get("section_id") as string | null;
+
const recordingDateStr = formData.get("recording_date") as
+
| string
+
| null;
if (!file) throw ValidationErrors.missingField("audio");
···
const uploadDir = "./uploads";
await Bun.write(`${uploadDir}/${filename}`, file);
+
// Parse recording date (default to current time if not provided)
+
const recordingDate = recordingDateStr
+
? Number.parseInt(recordingDateStr, 10)
+
: Math.floor(Date.now() / 1000);
+
// Create database record (without meeting_time_id - will be set later via PATCH)
db.run(
-
"INSERT INTO transcriptions (id, user_id, class_id, meeting_time_id, section_id, filename, original_filename, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+
"INSERT INTO transcriptions (id, user_id, class_id, meeting_time_id, section_id, filename, original_filename, status, recording_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
transcriptionId,
user.id,
···
filename,
file.name,
"pending",
+
recordingDate,
],
);
+1 -1
src/lib/classes.ts
···
`SELECT id, user_id, meeting_time_id, section_id, filename, original_filename, status, progress, error_message, created_at, updated_at
FROM transcriptions
WHERE class_id = ?
-
ORDER BY created_at DESC`,
+
ORDER BY recording_date DESC, created_at DESC`,
)
.all(classId);
}