55 getSubmissionStatusMapWithId ,
66 getSubmissionStatusMapWithName ,
77} from '$lib/services/submission_status' ;
8- import { getTasks , getTask } from '$lib/services/tasks' ;
8+ import { getTasks , getTasksWithSelectedTaskIds , getTask } from '$lib/services/tasks' ;
99import { getUser } from '$lib/services/users' ;
1010import * as answer_crud from '$lib/services/answers' ;
1111
@@ -19,7 +19,7 @@ import type { User } from '@prisma/client';
1919import type { TaskAnswer } from '$lib/types/answer' ;
2020import type { Task } from '$lib/types/task' ;
2121import type { TaskResult , TaskResults , Tasks } from '$lib/types/task' ;
22- import type { WorkBookTaskBase , WorkBookTasksBase } from '$lib/types/workbook' ;
22+ import type { WorkBookTasksBase } from '$lib/types/workbook' ;
2323import type { FloatingMessages } from '$lib/types/floating_message' ;
2424
2525import { NOT_FOUND } from '$lib/constants/http-response-status-codes' ;
@@ -183,44 +183,90 @@ export async function getTaskResultsOnlyResultExists(
183183}
184184
185185// Note: 個別の問題集を参照するときのみ使用する。
186- // Why : 未回答の問題も含めて取得するため、データ総量を抑えるためにも問題集の一覧( ユーザの回答を含む) を参照するときは上記のメソッドを使用する。
186+ // Why : 未回答の問題も含めて取得するため、データ総量を抑えるためにも問題集の一覧( ユーザの回答を含む) を参照するときは上記のメソッドを使用する。
187187export async function getTaskResultsByTaskId (
188188 workBookTasks : WorkBookTasksBase ,
189189 userId : string ,
190190) : Promise < Map < string , TaskResult > > {
191- const taskResultsWithTaskId = workBookTasks . map ( ( workBookTask : WorkBookTaskBase ) =>
192- getTaskResultWithErrorHandling ( workBookTask . taskId , userId ) . then ( ( taskResult : TaskResult ) => ( {
193- taskId : workBookTask . taskId ,
194- taskResult : taskResult ,
195- } ) ) ,
196- ) ;
191+ const startTime = Date . now ( ) ;
192+
193+ // Step 1: Extract task IDs with type-safe filtering
194+ const taskIds = workBookTasks
195+ . map ( ( workBookTask ) => workBookTask . taskId )
196+ . filter ( ( id ) : id is string => id !== null && id !== undefined ) ;
197+
198+ if ( taskIds . length === 0 ) {
199+ return new Map ( ) ;
200+ }
201+
202+ // Step 2 & 3: Bulk fetch all tasks and answers (2 query)
203+ const tasks = await getTasksWithSelectedTaskIds ( taskIds ) ;
204+ const answers = userId ? await answer_crud . getAnswersWithSelectedTaskIds ( taskIds , userId ) : [ ] ;
205+
206+ // Step 4: Create Maps for O(1) lookup
207+ const tasksMap = new Map ( tasks . map ( ( task : Task ) => [ task . task_id , task ] ) ) ;
208+ const answersMap = new Map ( answers . map ( ( answer ) => [ answer . task_id , answer ] ) ) ;
209+ const taskResultsMap = new Map < string , TaskResult > ( ) ;
210+
211+ // Step 5: Merge in memory using mergeTaskAndAnswer
212+ for ( const taskId of taskIds ) {
213+ const task = tasksMap . get ( taskId ) ;
214+
215+ if ( ! task ) {
216+ console . warn ( `Not found task: ${ taskId } in database` ) ;
217+ continue ;
218+ }
197219
198- const taskResultsMap = ( await Promise . all ( taskResultsWithTaskId ) ) . reduce (
199- ( map , { taskId, taskResult } : { taskId : string ; taskResult : TaskResult } ) =>
200- map . set ( taskId , taskResult ) ,
201- new Map < string , TaskResult > ( ) ,
220+ const answer = answersMap . get ( taskId ) ;
221+ const taskResult = mergeTaskAndAnswer ( task , userId , answer ) ;
222+
223+ taskResultsMap . set ( taskId , taskResult ) ;
224+ }
225+
226+ const duration = Date . now ( ) - startTime ;
227+ console . log (
228+ `[getTaskResultsByTaskId] Loaded ${ taskIds . length } tasks in ${ duration } ms (${ answers . length } answers)` ,
202229 ) ;
203230
204231 return taskResultsMap ;
205232}
206233
207- async function getTaskResultWithErrorHandling ( taskId : string , userId : string ) : Promise < TaskResult > {
208- try {
209- return await getTaskResult ( taskId , userId ) ;
210- } catch ( error ) {
211- console . error ( `Failed to get task result for taskId ${ taskId } :` , error ) ;
212- return await handleTaskResultError ( taskId , userId ) ;
234+ /**
235+ * Merge task and answer to create TaskResult
236+ * Extracted common logic from getTaskResult (excluding DB access)
237+ *
238+ * @param task - Task object from database
239+ * @param userId - User ID for creating TaskResult
240+ * @param answer - TaskAnswer object from database (can be null or undefined)
241+ * @returns TaskResult with merged data
242+ */
243+ function mergeTaskAndAnswer (
244+ task : Task ,
245+ userId : string ,
246+ answer : TaskAnswer | null | undefined ,
247+ ) : TaskResult {
248+ const taskResult = createDefaultTaskResult ( userId , task ) ;
249+
250+ if ( ! answer ) {
251+ return taskResult ;
213252 }
214- }
215253
216- async function handleTaskResultError ( taskId : string , userId : string ) : Promise < TaskResult > {
217- try {
218- const task : Tasks = await getTask ( taskId ) ;
219- return await createDefaultTaskResult ( userId , task [ 0 ] ) ;
220- } catch ( innerError ) {
221- console . error ( `Failed to create a default task result for taskId ${ taskId } :` , innerError ) ;
222- throw new Error ( `問題id: ${ taskId } の作成に失敗しました。` ) ;
254+ const status = statusById . get ( answer . status_id ) ;
255+
256+ if ( status ) {
257+ taskResult . status_id = status . id ;
258+ taskResult . status_name = status . status_name ;
259+ taskResult . submission_status_image_path = status . image_path ;
260+ taskResult . submission_status_label_name = status . label_name ;
261+ taskResult . is_ac = status . is_ac ;
262+ taskResult . user_id = userId ;
263+
264+ if ( answer . updated_at ) {
265+ taskResult . updated_at = answer . updated_at ;
266+ }
223267 }
268+
269+ return taskResult ;
224270}
225271
226272export function createDefaultTaskResult ( userId : string , task : Task ) : TaskResult {
@@ -242,29 +288,17 @@ export function createDefaultTaskResult(userId: string, task: Task): TaskResult
242288 return taskResult ;
243289}
244290
291+ // Note: This function will be deprecated in the future in favor of bulk operations (getTaskResultsByTaskId)
245292export async function getTaskResult ( slug : string , userId : string ) {
246293 const task = await getTask ( slug ) ;
247294
248295 if ( ! task || task . length === 0 ) {
249296 error ( NOT_FOUND , `問題 ${ slug } は見つかりませんでした。` ) ;
250297 }
251298
252- const taskResult = createDefaultTaskResult ( userId , task [ 0 ] ) ;
253- const taskanswer : TaskAnswer | null = await answer_crud . getAnswer ( slug , userId ) ;
254-
255- if ( ! taskanswer ) {
256- return taskResult ;
257- }
258-
259- const status = statusById . get ( taskanswer . status_id ) ;
260- taskResult . status_id = status . id ;
261- taskResult . status_name = status . status_name ;
262- taskResult . submission_status_image_path = status . image_path ;
263- taskResult . submission_status_label_name = status . label_name ;
264- taskResult . is_ac = status . is_ac ;
265- taskResult . user_id = userId ;
299+ const taskAnswer : TaskAnswer | null = await answer_crud . getAnswer ( slug , userId ) ;
266300
267- return taskResult ;
301+ return mergeTaskAndAnswer ( task [ 0 ] , userId , taskAnswer ) ;
268302}
269303
270304export async function updateTaskResult ( taskId : string , submissionStatus : string , userId : string ) {
0 commit comments