Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/Http/Controllers/StudyTrackerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]);

Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions app/Models/DailyStudyLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

class DailyStudyLog extends Model
{
public const MAX_DAILY_SECONDS = 57600; // 16 hours

protected $fillable = [
'user_id',
'study_date',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// 16 hours limit in seconds (16 * 3600 = 57,600)
DB::table('daily_study_logs')
->where('total_seconds', '>', 57600)
->delete();
}

/**
* Reverse the migrations.
*/
public function down(): void
{
// Deleted invalid excessive logs cannot be reconstructed
}
};
21 changes: 16 additions & 5 deletions resources/js/components/tracker/StopwatchWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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).
</p>
</div>
Expand Down
11 changes: 10 additions & 1 deletion resources/js/pages/Leaderboard/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -801,9 +801,18 @@ onBeforeUnmount(() => {
class="font-semibold text-slate-800 dark:text-gray-200"
>দৈনিক ক্যাপ:</strong
>
অবাস্তব সময় যোগ রোধ করতে এক ক্যালেন্ডার দিনে সর্বোচ্চ ২৪
অবাস্তব সময় যোগ রোধ করতে এক ক্যালেন্ডার দিনে সর্বোচ্চ ১৬
ঘণ্টার বেশি লগ করা সম্ভব নয়।
</li>
<li>
<strong
class="font-semibold text-slate-800 dark:text-gray-200"
>ফেয়ার-প্লে পলিসি:</strong
>
অস্বাভাবিক বা সন্দেহজনক কোনো লগিং কার্যকলাপ পরিলক্ষিত
হলে সংশ্লিষ্ট ব্যবহারকারীকে স্বয়ংক্রিয়ভাবে লিডারবোর্ড
র‍্যাঙ্কিং থেকে বাদ দেওয়া হবে।
</li>
<li>
<strong
class="font-semibold text-slate-800 dark:text-gray-200"
Expand Down
31 changes: 31 additions & 0 deletions tests/Feature/StudyTrackerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading