Skip to content

Commit

Permalink
feat: handle get object by key with/without timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
SarahTeoh committed Jun 26, 2024
1 parent 4012402 commit df3cdca
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 0 deletions.
13 changes: 13 additions & 0 deletions app/Http/Controllers/V1/KeyValueController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Http\Controllers\Controller;
use App\Http\Resources\KeyValueResource;
use App\Interfaces\KeyValueServiceInterface;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class KeyValueController extends Controller
Expand All @@ -20,4 +21,16 @@ public function index(): ResourceCollection

return KeyValueResource::collection($data);
}

/**
* Accept a key and return the corresponding latest value.
* When given a key AND a timestamp,return whatever the value of the key at the time was.
*/
public function show(Request $request, string $key): KeyValueResource
{
$timestamp = $request->query('timestamp');
$data = $this->keyValueService->getValue($key, $timestamp);

return new KeyValueResource($data);
}
}
4 changes: 4 additions & 0 deletions app/Interfaces/KeyValueRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@

interface KeyValueRepositoryInterface
{
public function getKeyLatest(string $key);

public function getByTimestamp(string $key, int $timestamp);

public function getAll();
}
2 changes: 2 additions & 0 deletions app/Interfaces/KeyValueServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@

interface KeyValueServiceInterface
{
public function getValue(string $key, ?string $timestamp = '');

public function getAll();
}
15 changes: 15 additions & 0 deletions app/Repositories/DynamoDbKeyValueRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@

use App\Interfaces\KeyValueRepositoryInterface;
use App\Models\KeyValue;
use BaoPham\DynamoDb\RawDynamoDbQuery;
use Illuminate\Database\Eloquent\Collection;

class DynamoDbKeyValueRepository implements KeyValueRepositoryInterface
{
public function __construct(private KeyValue $model) {}

public function getKeyLatest(string $key)
{
// @phpstan-ignore-next-line
return $this->model->where('key', $key)
->decorate(function (RawDynamoDbQuery $raw) {
$raw->query['ScanIndexForward'] = false;
})->firstOrFail();
}

public function getByTimestamp(string $key, int $timestamp)
{
return $this->model->findOrFail(['key' => $key, 'timestamp' => $timestamp]);
}

public function getAll(): Collection
{
return $this->model->all();
Expand Down
11 changes: 11 additions & 0 deletions app/Services/KeyValueService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ class KeyValueService implements KeyValueServiceInterface
{
public function __construct(private KeyValueRepositoryInterface $keyValueRepository) {}

public function getValue(string $key, ?string $timestamp = '')
{
if ($timestamp) {
$data = $this->keyValueRepository->getByTimestamp($key, (int) $timestamp);
} else {
$data = $this->keyValueRepository->getKeyLatest($key);
}

return $data;
}

public function getAll()
{
return $this->keyValueRepository->getAll();
Expand Down
1 change: 1 addition & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
})->middleware('auth:sanctum');

Route::get('get_all_records', [KeyValueController::class, 'index']);
Route::get('object/{key}', [KeyValueController::class, 'show']);
27 changes: 27 additions & 0 deletions tests/Feature/Controllers/V1/KeyValueControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,30 @@
],
]);
});

it('can fetch the latest value for a key', function () {
$response = $this->get('/api/v1/object/mykey');

$response->assertStatus(200)
->assertJson([
'data' => [
'key' => 'mykey',
'value' => 'latest_value',
],
]);
});

it('can fetch the value for a key at a specific timestamp', function () {
$timestamp = 1625236523;

$response = $this->get('/api/v1/object/mykey?timestamp=' . $timestamp);

$response->assertStatus(200)
->assertJson([
'data' => [
'key' => 'mykey',
'value' => 'value1',
'timestamp' => $timestamp,
],
]);
});
20 changes: 20 additions & 0 deletions tests/Unit/Repositories/DynamoDbKeyValueRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@
$this->repository = new DynamoDbKeyValueRepository($this->mockModel);
});

it('can get the latest value for a key', function () {
$mockedKeyValue = new KeyValue(['key' => 'test_key', 'value' => 'latest_value']);

$this->mockModel->shouldReceive('where')->once()->with('key', 'test_key')->andReturnSelf();
$this->mockModel->shouldReceive('decorate')->once()->andReturnSelf();
$this->mockModel->shouldReceive('firstOrFail')->once()->andReturn($mockedKeyValue);

$result = $this->repository->getKeyLatest('test_key');

expect($result)->toBe($mockedKeyValue);
});

it('throws ModelNotFoundException when getting by timestamp for non-existing key and timestamp', function () {
$this->mockModel->shouldReceive('findOrFail')->once()->andThrow(ModelNotFoundException::class);

$this->expectException(ModelNotFoundException::class);

$this->repository->getByTimestamp('non_existing_key', time());
});

it('can get all key-value pairs', function () {
$mockedCollection = new Collection([
new KeyValue(['key' => 'key1', 'value' => 'value1']),
Expand Down
18 changes: 18 additions & 0 deletions tests/Unit/Services/KeyValueServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@

use App\Interfaces\KeyValueRepositoryInterface;
use App\Services\KeyValueService;
use Carbon\CarbonImmutable;

beforeEach(function () {
$this->mockRepository = mock(KeyValueRepositoryInterface::class);
$this->service = new KeyValueService($this->mockRepository);
});

it('can get the latest value for a key', function () {
$this->mockRepository->shouldReceive('getKeyLatest')->once()->with('test_key')->andReturn('latest_value');

$result = $this->service->getValue('test_key');

expect($result)->toBe('latest_value');
});

it('can get the value for a key at a specific timestamp', function () {
$timestamp = CarbonImmutable::now('UTC')->timestamp;
$this->mockRepository->shouldReceive('getByTimestamp')->once()->with('test_key', $timestamp)->andReturn('value_at_timestamp');

$result = $this->service->getValue('test_key', (string) $timestamp);

expect($result)->toBe('value_at_timestamp');
});

it('can get all key-value pairs', function () {
$this->mockRepository->shouldReceive('getAll')->once()->andReturn(['key1' => 'value1', 'key2' => 'value2']);

Expand Down

0 comments on commit df3cdca

Please sign in to comment.