Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add api endpoints for context #2914

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
12 changes: 12 additions & 0 deletions sourcecode/hub/app/Events/ContextDeleting.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Events;

use App\Models\Context;

final readonly class ContextDeleting
{
public function __construct(public Context $context) {}
}
2 changes: 2 additions & 0 deletions sourcecode/hub/app/Http/Controllers/Api/ContentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public function store(ContentRequest $request): JsonResponse
$content->fill($request->validated());
$content->saveOrFail();

$content->contexts()->sync($request->getContexts());

foreach ($request->getRoles() as ['user' => $user, 'role' => $role]) {
$content->users()->attach($user, ['role' => $role]);
}
Expand Down
55 changes: 55 additions & 0 deletions sourcecode/hub/app/Http/Controllers/Api/ContextController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Requests\Api\ContextRequest;
use App\Models\Context;
use App\Transformers\ContextTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;

use function fractal;

final readonly class ContextController
{
public function __construct(
private ContextTransformer $contextTransformer,
) {}

public function index(): JsonResponse
{
$contexts = Context::paginate();

return fractal($contexts)
->transformWith($this->contextTransformer)
->paginateWith(new IlluminatePaginatorAdapter($contexts))
->respond();
}

public function show(Context $context): JsonResponse
{
return fractal($context)
->transformWith($this->contextTransformer)
->respond();
}

public function store(ContextRequest $request): JsonResponse
{
$context = new Context($request->validated());
$context->save();

return fractal($context)
->transformWith($this->contextTransformer)
->respond();
}

public function destroy(Context $context): Response
{
$context->delete();

return response()->noContent();
}
}
18 changes: 18 additions & 0 deletions sourcecode/hub/app/Http/Requests/Api/ContentRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
namespace App\Http\Requests\Api;

use App\Enums\ContentRole;
use App\Models\Context;
use App\Models\User;
use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

use function array_map;

final class ContentRequest extends FormRequest
{
/**
Expand All @@ -20,6 +23,10 @@ public function rules(Gate $gate): array
return [
'shared' => ['sometimes', 'boolean'],

'contexts' => [
Rule::exists(Context::class, 'name'),
],

'created_at' => [
Rule::prohibitedIf(fn() => $gate->denies('admin')),
'sometimes',
Expand Down Expand Up @@ -47,6 +54,17 @@ public function rules(Gate $gate): array
];
}

/**
* @return array<int, Context>
*/
public function getContexts(): array
{
return array_map(
fn(string $name) => Context::where('name', $name)->firstOrFail(),
$this->validated('contexts', []),
);
}

/**
* @return array<int, array{user: User, role: ContentRole}>
*/
Expand Down
22 changes: 22 additions & 0 deletions sourcecode/hub/app/Http/Requests/Api/ContextRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Http\Requests\Api;

use App\Models\Context;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class ContextRequest extends FormRequest
{
/**
* @return array<mixed>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'regex:/^\w+$/', Rule::unique(Context::class, 'name')],
];
}
}
17 changes: 17 additions & 0 deletions sourcecode/hub/app/Listeners/ContextListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace App\Listeners;

use App\Events\ContextDeleting;

final readonly class ContextListener
{
public function handleDeleting(ContextDeleting $event): void
{
$event->context->contents()->detach();

$event->context->platforms()->detach();
}
}
27 changes: 27 additions & 0 deletions sourcecode/hub/app/Models/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

namespace App\Models;

use App\Events\ContextDeleting;
use App\Support\HasUlidsFromCreationDate;
use Database\Factories\ContextFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

/**
* A "context" is used to grant access based on externally defined criteria.
Expand All @@ -20,7 +22,32 @@ class Context extends Model

public const UPDATED_AT = null;

protected $perPage = 100;

protected $fillable = [
'name',
];

/**
* @var array<string, class-string>
*/
protected $dispatchesEvents = [
'deleting' => ContextDeleting::class,
];

/**
* @return BelongsToMany<Content, $this>
*/
public function contents(): BelongsToMany
{
return $this->belongsToMany(Content::class, 'content_context');
}

/**
* @return BelongsToMany<LtiPlatform, $this>
*/
public function platforms(): BelongsToMany
{
return $this->belongsToMany(LtiPlatform::class, 'lti_platform_context');
}
}
8 changes: 8 additions & 0 deletions sourcecode/hub/app/Transformers/ContentTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ final class ContentTransformer extends TransformerAbstract
{
/** @var string[] */
protected array $availableIncludes = [
'contexts',
'roles',
'versions',
];

/** @var string[] */
protected array $defaultIncludes = [
'contexts',
'roles',
'versions',
];

public function __construct(
private readonly ContentVersionTransformer $contentVersionTransformer,
private readonly ContextTransformer $contextTransformer,
) {}

/**
Expand All @@ -45,6 +48,11 @@ public function transform(Content $content): array
];
}

public function includeContexts(Content $content): Collection
{
return $this->collection($content->contexts, $this->contextTransformer);
}

public function includeRoles(Content $content): Collection
{
return $this->collection($content->users, function (User $user) {
Expand Down
25 changes: 25 additions & 0 deletions sourcecode/hub/app/Transformers/ContextTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace App\Transformers;

use App\Models\Context;
use League\Fractal\TransformerAbstract;

final class ContextTransformer extends TransformerAbstract
{
/**
* @return array<string, mixed>
*/
public function transform(Context $context): array
{
return [
'id' => $context->id,
'name' => $context->name,
'links' => [
'self' => route('api.contexts.show', [$context]),
],
];
}
}
19 changes: 19 additions & 0 deletions sourcecode/hub/routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Http\Controllers\Api\ContentController;
use App\Http\Controllers\Api\ContentVersionController;
use App\Http\Controllers\Api\ContentViewController;
use App\Http\Controllers\Api\ContextController;
use App\Http\Controllers\Api\LtiToolController;
use App\Http\Controllers\Api\UserController;
use Illuminate\Support\Facades\Route;
Expand Down Expand Up @@ -65,6 +66,24 @@
->can('edit', 'apiContent');
});

Route::where(['context' => '\w+'])->name('api.contexts.')->middleware(['can:admin'])->group(function () {
Route::get('/contexts')
->uses([ContextController::class, 'index'])
->name('index');

Route::get('/contexts/{context:name}')
->uses([ContextController::class, 'show'])
->name('show');

Route::post('/contexts')
->uses([ContextController::class, 'store'])
->name('create');

Route::delete('/contexts/{context:name}')
->uses([ContextController::class, 'destroy'])
->name('destroy');
});

Route::whereUlid('tool')->name('api.lti-tools.')->middleware(['can:admin'])->group(function () {
Route::get('/lti-tools')
->uses([LtiToolController::class, 'index']);
Expand Down
16 changes: 16 additions & 0 deletions sourcecode/hub/tests/Feature/Api/ContentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Models\Content;
use App\Models\ContentVersion;
use App\Models\ContentView;
use App\Models\Context;
use App\Models\LtiTool;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
Expand Down Expand Up @@ -163,11 +164,15 @@ public function testShowsContent(): void

public function testStoresContent(): void
{
$context = Context::factory()->name('my_context')->create();
$owner = User::factory()->create();

$data = [
'created_at' => $this->faker->dateTime->format('c'),
'shared' => $this->faker->boolean,
'contexts' => [
'my_context',
],
'roles' => [
[
'user' => $owner->id,
Expand Down Expand Up @@ -196,6 +201,17 @@ public function testStoresContent(): void
->where('shared', $data['shared'])
->has('links.self')
->has('versions')
->where('contexts', [
'data' => [
[
'id' => $context->id,
'name' => 'my_context',
'links' => [
'self' => 'https://hub-test.edlib.test/api/contexts/my_context',
],
],
],
])
->where('roles', [
'data' => [
[
Expand Down
Loading
Loading