From 3d8d8ba294b86490ace25724a776f65165af5482 Mon Sep 17 00:00:00 2001
From: TR Tajim
Date: Fri, 16 Oct 2026 12:47:52 +0600
Subject: [PATCH 1/2] feat(tracker): enforce 16-hour daily study limit and drop
excessive records
---
.../Controllers/StudyTrackerController.php | 4 +--
app/Models/DailyStudyLog.php | 2 ++
...124500_drop_excessive_daily_study_logs.php | 26 ++++++++++++++++
.../js/components/tracker/StopwatchWidget.vue | 21 ++++++++++---
resources/js/pages/Leaderboard/Index.vue | 2 +-
tests/Feature/StudyTrackerTest.php | 31 +++++++++++++++++++
6 files changed, 78 insertions(+), 8 deletions(-)
create mode 100644 database/migrations/2026_10_16_124500_drop_excessive_daily_study_logs.php
diff --git a/app/Http/Controllers/StudyTrackerController.php b/app/Http/Controllers/StudyTrackerController.php
index 77f165d8..945df03f 100644
--- a/app/Http/Controllers/StudyTrackerController.php
+++ b/app/Http/Controllers/StudyTrackerController.php
@@ -130,7 +130,7 @@ public function logTime(Request $request)
}
$validated = $request->validate([
- 'seconds' => 'required|integer|min:1|max:86400',
+ 'seconds' => 'required|integer|min:1|max:'.DailyStudyLog::MAX_DAILY_SECONDS,
'date' => 'nullable|date_format:Y-m-d|before_or_equal:today',
]);
@@ -146,7 +146,7 @@ public function logTime(Request $request)
]
);
- $maxAllowed = max(0, 86400 - (int) $log->total_seconds);
+ $maxAllowed = max(0, DailyStudyLog::MAX_DAILY_SECONDS - (int) $log->total_seconds);
if ($maxAllowed > 0) {
$secondsToAdd = min((int) $validated['seconds'], $maxAllowed);
$log->increment('total_seconds', $secondsToAdd);
diff --git a/app/Models/DailyStudyLog.php b/app/Models/DailyStudyLog.php
index c205b85f..4651f66c 100644
--- a/app/Models/DailyStudyLog.php
+++ b/app/Models/DailyStudyLog.php
@@ -9,6 +9,8 @@
class DailyStudyLog extends Model
{
+ public const MAX_DAILY_SECONDS = 57600; // 16 hours
+
protected $fillable = [
'user_id',
'study_date',
diff --git a/database/migrations/2026_10_16_124500_drop_excessive_daily_study_logs.php b/database/migrations/2026_10_16_124500_drop_excessive_daily_study_logs.php
new file mode 100644
index 00000000..ed039aee
--- /dev/null
+++ b/database/migrations/2026_10_16_124500_drop_excessive_daily_study_logs.php
@@ -0,0 +1,26 @@
+where('total_seconds', '>', 57600)
+ ->delete();
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ // Deleted invalid excessive logs cannot be reconstructed
+ }
+};
diff --git a/resources/js/components/tracker/StopwatchWidget.vue b/resources/js/components/tracker/StopwatchWidget.vue
index 653f77cf..73c54e01 100644
--- a/resources/js/components/tracker/StopwatchWidget.vue
+++ b/resources/js/components/tracker/StopwatchWidget.vue
@@ -166,14 +166,19 @@ function resetTimer() {
localStorage.removeItem(STORAGE_KEY_ACCUMULATED);
}
+const MAX_DAILY_SECONDS = 57600; // 16 hours max per calendar day
+
const maxMinutesAllowedForToday = computed(() => {
- const remainingSeconds = Math.max(0, 86400 - props.todaySeconds);
+ const remainingSeconds = Math.max(
+ 0,
+ MAX_DAILY_SECONDS - props.todaySeconds,
+ );
return Math.floor(remainingSeconds / 60);
});
const isDayMaxReached = computed(() => {
- return props.todaySeconds >= 86400;
+ return props.todaySeconds >= MAX_DAILY_SECONDS;
});
function triggerStopwatchSave() {
@@ -187,7 +192,10 @@ function triggerStopwatchSave() {
return;
}
- const remainingAllowed = Math.max(0, 86400 - props.todaySeconds);
+ const remainingAllowed = Math.max(
+ 0,
+ MAX_DAILY_SECONDS - props.todaySeconds,
+ );
const secondsToLog = Math.min(elapsedSeconds.value, remainingAllowed);
if (secondsToLog <= 0) {
@@ -212,7 +220,10 @@ function triggerQuickLog(minutes: number) {
}
const requestedSeconds = minutes * 60;
- const remainingAllowed = Math.max(0, 86400 - props.todaySeconds);
+ const remainingAllowed = Math.max(
+ 0,
+ MAX_DAILY_SECONDS - props.todaySeconds,
+ );
const secondsToLog = Math.min(requestedSeconds, remainingAllowed);
if (secondsToLog <= 0) {
@@ -639,7 +650,7 @@ const todayFormatted = computed(() => {
class="mt-1.5 text-[11px] font-medium text-red-500 dark:text-red-400"
>
You can log a maximum of
- {{ maxMinutesAllowedForToday }} minutes today (24h
+ {{ maxMinutesAllowedForToday }} minutes today (16h
daily limit).
diff --git a/resources/js/pages/Leaderboard/Index.vue b/resources/js/pages/Leaderboard/Index.vue
index ae91836f..00192229 100644
--- a/resources/js/pages/Leaderboard/Index.vue
+++ b/resources/js/pages/Leaderboard/Index.vue
@@ -801,7 +801,7 @@ onBeforeUnmount(() => {
class="font-semibold text-slate-800 dark:text-gray-200"
>দৈনিক ক্যাপ:
- অবাস্তব সময় যোগ রোধ করতে এক ক্যালেন্ডার দিনে সর্বোচ্চ ২৪
+ অবাস্তব সময় যোগ রোধ করতে এক ক্যালেন্ডার দিনে সর্বোচ্চ ১৬
ঘণ্টার বেশি লগ করা সম্ভব নয়।
diff --git a/tests/Feature/StudyTrackerTest.php b/tests/Feature/StudyTrackerTest.php
index b6eeddca..a5083026 100644
--- a/tests/Feature/StudyTrackerTest.php
+++ b/tests/Feature/StudyTrackerTest.php
@@ -140,6 +140,37 @@
]);
});
+test('logging study time cannot exceed daily max cap of 16 hours', function () {
+ $user = User::factory()->create();
+
+ // Rejects request exceeding max daily limit in single call
+ $this->actingAs($user)
+ ->post('/tracker/log-time', ['seconds' => DailyStudyLog::MAX_DAILY_SECONDS + 1])
+ ->assertSessionHasErrors('seconds');
+
+ // Allows logging up to max daily limit
+ $this->actingAs($user)
+ ->post('/tracker/log-time', ['seconds' => DailyStudyLog::MAX_DAILY_SECONDS])
+ ->assertRedirect();
+
+ $this->assertDatabaseHas('daily_study_logs', [
+ 'user_id' => $user->id,
+ 'study_date' => Carbon::today()->format('Y-m-d'),
+ 'total_seconds' => DailyStudyLog::MAX_DAILY_SECONDS,
+ ]);
+
+ // Subsequent logs within the same day are capped at max
+ $this->actingAs($user)
+ ->post('/tracker/log-time', ['seconds' => 600])
+ ->assertRedirect();
+
+ $log = DailyStudyLog::where('user_id', $user->id)
+ ->where('study_date', Carbon::today()->format('Y-m-d'))
+ ->first();
+
+ expect($log->total_seconds)->toBe(DailyStudyLog::MAX_DAILY_SECONDS);
+});
+
test('tracker only includes trackable subjects and trackable nodes flatly', function () {
$trackableSubject = Subject::create([
'name' => 'Trackable Subject',
From 833f98445af52b796d2cd147e23c12b303705cd1 Mon Sep 17 00:00:00 2001
From: TR Tajim
Date: Fri, 16 Oct 2026 12:50:29 +0600
Subject: [PATCH 2/2] feat(leaderboard): add fair-play notice for unusual
logging in info modal
---
resources/js/pages/Leaderboard/Index.vue | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/resources/js/pages/Leaderboard/Index.vue b/resources/js/pages/Leaderboard/Index.vue
index 00192229..157c6a5b 100644
--- a/resources/js/pages/Leaderboard/Index.vue
+++ b/resources/js/pages/Leaderboard/Index.vue
@@ -804,6 +804,15 @@ onBeforeUnmount(() => {
অবাস্তব সময় যোগ রোধ করতে এক ক্যালেন্ডার দিনে সর্বোচ্চ ১৬
ঘণ্টার বেশি লগ করা সম্ভব নয়।
+
+ ফেয়ার-প্লে পলিসি:
+ অস্বাভাবিক বা সন্দেহজনক কোনো লগিং কার্যকলাপ পরিলক্ষিত
+ হলে সংশ্লিষ্ট ব্যবহারকারীকে স্বয়ংক্রিয়ভাবে লিডারবোর্ড
+ র্যাঙ্কিং থেকে বাদ দেওয়া হবে।
+