/*********************************************************************************************************************************************************************** * File Name: playback_position.service.js * Type of Program: Service * Description: Last-known video/audio playback position per (user, lesson, block) — entirely * decoupled from CompletionRequirement, tracked for ANY video/audio block regardless * of whether the lesson has a watch-type completion requirement configured. Powers * "resume where I left off" only; no anti-cheat/wall-clock validation here since * there's nothing being gated — see completion_requirements.service.js for that. * * recordPlaybackPosition — last-write-wins upsert (not a ratcheted max — a deliberate rewind * should resume there, not snap back to a prior high-water mark). * getPlaybackPositions — { [block_id]: percent } for every block tracked on a lesson. * * Author: Kenneth Obsequio (@lash0000) * Date Created: Jul. 17, 2026 ***********************************************************************************************************************************************************************/ 'use strict'; const MediaPlaybackPosition = require('../models/courses/media_playback_position.mdl'); async function recordPlaybackPosition(userId, { lessonId, blockId, percent }) { if (!blockId) return; await MediaPlaybackPosition.upsert({ user_id: userId, lesson_id: lessonId, block_id: blockId, percent: Math.min(100, Math.max(0, Math.round(percent))), }, { conflictFields: ['user_id', 'lesson_id', 'block_id'] }); } async function getPlaybackPositions(userId, lessonId) { const rows = await MediaPlaybackPosition.findAll({ where: { user_id: userId, lesson_id: lessonId }, attributes: ['block_id', 'percent'], }); return Object.fromEntries(rows.map((r) => [r.block_id, r.percent])); } module.exports = { recordPlaybackPosition, getPlaybackPositions };