diff --git a/framework/core/src/Http/AccessToken.php b/framework/core/src/Http/AccessToken.php index 75f4afe78..3a68dd38d 100644 --- a/framework/core/src/Http/AccessToken.php +++ b/framework/core/src/Http/AccessToken.php @@ -63,6 +63,12 @@ class AccessToken extends AbstractModel */ protected static $lifetime = 0; + /** + * Difference from the current `last_activity_at` attribute value before `updateLastSeen()` + * will update the attribute on the DB. Measured in seconds. + */ + private const LAST_ACTIVITY_UPDATE_DIFF = 90; + /** * Generate an access token for the specified user. * @@ -95,7 +101,11 @@ class AccessToken extends AbstractModel */ public function touch(ServerRequestInterface $request = null) { - $this->last_activity_at = Carbon::now(); + $now = Carbon::now(); + + if ($this->last_activity_at === null || $this->last_activity_at->diffInSeconds($now) > AccessToken::LAST_ACTIVITY_UPDATE_DIFF) { + $this->last_activity_at = $now; + } if ($request) { $this->last_ip_address = $request->getAttribute('ipAddress'); diff --git a/framework/core/src/User/User.php b/framework/core/src/User/User.php index 73675ec7f..8981f3310 100644 --- a/framework/core/src/User/User.php +++ b/framework/core/src/User/User.php @@ -120,6 +120,12 @@ class User extends AbstractModel */ protected static $passwordCheckers; + /** + * Difference from the current `last_seen` attribute value before `updateLastSeen()` + * will update the attribute on the DB. Measured in seconds. + */ + private const LAST_SEEN_UPDATE_DIFF = 180; + /** * Boot the model. * @@ -564,7 +570,11 @@ class User extends AbstractModel */ public function updateLastSeen() { - $this->last_seen_at = Carbon::now(); + $now = Carbon::now(); + + if ($this->last_seen_at === null || $this->last_seen_at->diffInSeconds($now) > User::LAST_SEEN_UPDATE_DIFF) { + $this->last_seen_at = $now; + } return $this; } diff --git a/framework/core/tests/integration/api/users/UpdateTest.php b/framework/core/tests/integration/api/users/UpdateTest.php index 2681c50d3..35da918b7 100644 --- a/framework/core/tests/integration/api/users/UpdateTest.php +++ b/framework/core/tests/integration/api/users/UpdateTest.php @@ -12,6 +12,7 @@ namespace Flarum\Tests\integration\api\users; use Carbon\Carbon; use Flarum\Testing\integration\RetrievesAuthorizedUsers; use Flarum\Testing\integration\TestCase; +use Flarum\User\User; class UpdateTest extends TestCase { @@ -33,6 +34,15 @@ class UpdateTest extends TestCase 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', // BCrypt hash for "too-obscure" 'email' => 'normal2@machine.local', 'is_email_confirmed' => 1, + 'last_seen_at' => Carbon::now()->subSecond(), + ], + [ + 'id' => 4, + 'username' => 'normal3', + 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', // BCrypt hash for "too-obscure" + 'email' => 'normal3@machine.local', + 'is_email_confirmed' => 1, + 'last_seen_at' => Carbon::now()->subHour(), ] ], ]); @@ -200,7 +210,7 @@ class UpdateTest extends TestCase 'relationships' => [ 'groups' => [ 'data' => [ - ['id'=> 1, 'type' => 'group'] + ['id' => 1, 'type' => 'group'] ] ] ], @@ -323,7 +333,7 @@ class UpdateTest extends TestCase 'relationships' => [ 'groups' => [ 'data' => [ - ['id'=> 1, 'type' => 'group'] + ['id' => 1, 'type' => 'group'] ] ] ], @@ -469,7 +479,7 @@ class UpdateTest extends TestCase 'relationships' => [ 'groups' => [ 'data' => [ - ['id'=> 4, 'type' => 'group'] + ['id' => 4, 'type' => 'group'] ] ] ], @@ -517,7 +527,7 @@ class UpdateTest extends TestCase 'relationships' => [ 'groups' => [ 'data' => [ - ['id'=> 1, 'type' => 'group'] + ['id' => 1, 'type' => 'group'] ] ] ], @@ -542,7 +552,7 @@ class UpdateTest extends TestCase 'relationships' => [ 'groups' => [ 'data' => [ - ['id'=> 1, 'type' => 'group'] + ['id' => 1, 'type' => 'group'] ] ] ], @@ -649,8 +659,7 @@ class UpdateTest extends TestCase 'data' => [ 'relationships' => [ 'groups' => [ - 'data' => [ - ] + 'data' => [] ] ], ] @@ -659,4 +668,46 @@ class UpdateTest extends TestCase ); $this->assertEquals(403, $response->getStatusCode()); } + + /** + * @test + */ + public function last_seen_not_updated_quickly() + { + $this->app(); + + $user = User::find(3); + + $response = $this->send( + $this->request('GET', '/api/users/3', [ + 'authenticatedAs' => 3, + 'json' => [], + ]) + ); + $body = json_decode($response->getBody(), true); + $last_seen = $body['data']['attributes']['lastSeenAt']; + + $this->assertTrue(Carbon::parse($last_seen)->equalTo($user->last_seen_at)); + } + + /** + * @test + */ + public function last_seen_updated_after_long_time() + { + $this->app(); + + $user = User::find(4); + + $response = $this->send( + $this->request('GET', '/api/users/4', [ + 'authenticatedAs' => 4, + 'json' => [], + ]) + ); + $body = json_decode($response->getBody(), true); + $last_seen = $body['data']['attributes']['lastSeenAt']; + + $this->assertFalse(Carbon::parse($last_seen)->equalTo($user->last_seen_at)); + } }