Skip to content
This repository was archived by the owner on Jun 29, 2021. It is now read-only.

Commit cbba378

Browse files
authored
Merge pull request #14 from ijpatricio/develop
Add Links domain
2 parents 6030ff0 + 1784a7d commit cbba378

23 files changed

+500
-38
lines changed

config/app.php

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
* Domains Service Providers...
180180
*/
181181
\Domains\Tags\TagsServiceProvider::class,
182+
\Domains\Links\LinksServiceProvider::class,
182183
],
183184

184185
/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Domains\Links\Controllers;
4+
5+
use App\Http\Controllers\Controller;
6+
use Domains\Links\Models\Link;
7+
use Domains\Links\Resources\LinkResource;
8+
use Illuminate\Database\Eloquent\Builder;
9+
use Illuminate\Http\Request;
10+
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
11+
12+
class LinksIndexController extends Controller
13+
{
14+
private Link $links;
15+
16+
public function __construct(Link $links)
17+
{
18+
$this->links = $links;
19+
}
20+
21+
public function __invoke(Request $request): AnonymousResourceCollection
22+
{
23+
$links = $this->links
24+
->when($request->input('include'), static function (Builder $query, string $includes) {
25+
return $query->with(
26+
explode(',', $includes)
27+
);
28+
})
29+
->approved()
30+
->simplePaginate();
31+
32+
return LinkResource::collection($links);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Domains\Links\Controllers;
4+
5+
use App\Http\Controllers\Controller;
6+
use Domains\Links\Models\Link;
7+
use Domains\Links\Requests\LinkStoreRequest;
8+
use Illuminate\Http\Response;
9+
10+
class LinksStoreController extends Controller
11+
{
12+
private Link $links;
13+
14+
public function __construct(Link $links)
15+
{
16+
$this->links = $links;
17+
}
18+
19+
public function __invoke(LinkStoreRequest $request): Response
20+
{
21+
$link = $this->links->create([
22+
'link' => $request->input('link'),
23+
'description' => $request->input('description'),
24+
'author_name' => $request->input('author_name'),
25+
'author_email' => $request->input('author_email'),
26+
'cover_image' => $request->file('cover_image')->store('cover_images'),
27+
]);
28+
29+
$link->tags()->attach($request->input('tags.*.id'));
30+
31+
return Response::create('', Response::HTTP_NO_CONTENT);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
use Domains\Links\Models\Link;
4+
use Domains\Tags\Models\Tag;
5+
use Illuminate\Database\Eloquent\Factory;
6+
use Illuminate\Http\UploadedFile;
7+
use Illuminate\Support\Carbon;
8+
9+
/** @var Factory $factory */
10+
$factory->define(Link::class, static fn (\Faker\Generator $faker) => [
11+
'link' => $faker->url,
12+
'description' => $faker->paragraph,
13+
'cover_image' => 'cover_images/' . UploadedFile::fake()->image('cover_image')->getFilename(),
14+
'author_name' => $faker->name,
15+
'author_email' => $faker->safeEmail,
16+
'created_at' => Carbon::now(),
17+
'approved_at' => null,
18+
]);
19+
20+
$factory->afterCreating(Link::class, static function (Link $link) {
21+
$link->tags()
22+
->attach(
23+
factory(Tag::class)->create()
24+
);
25+
});
26+
27+
$factory->state(Link::class, 'approved', static fn () => [
28+
'approved_at' => Carbon::now(),
29+
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class AddLinksTable extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('links', static function (Blueprint $table) {
12+
$table->id();
13+
$table->text('link');
14+
$table->text('description');
15+
$table->string('author_name');
16+
$table->string('author_email');
17+
$table->string('cover_image');
18+
$table->timestamps();
19+
$table->softDeletes();
20+
$table->timestamp('approved_at')->nullable();
21+
});
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class AddLinkTagTable extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('link_tag', static function (Blueprint $table) {
12+
$table->id();
13+
$table->foreignId('link_id')->constrained();
14+
$table->foreignId('tag_id')->constrained();
15+
});
16+
}
17+
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Domains\Links;
4+
5+
use Illuminate\Support\Facades\Route;
6+
use Illuminate\Support\ServiceProvider;
7+
8+
class LinksServiceProvider extends ServiceProvider
9+
{
10+
public function boot(): void
11+
{
12+
$this->loadFactoriesFrom(__DIR__ . '/Database/Factories');
13+
14+
$this->loadMigrationsFrom(__DIR__ . '/Database/Migrations');
15+
16+
$this->bootRoutes();
17+
}
18+
19+
private function bootRoutes(): void
20+
{
21+
Route::middleware(['api', 'throttle'])
22+
->group(fn () => $this->loadRoutesFrom(__DIR__ . '/Routes/api.php'));
23+
}
24+
}

domains/Links/Models/Link.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Domains\Links\Models;
4+
5+
use Domains\Tags\Models\Tag;
6+
use Illuminate\Database\Eloquent\Builder;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
9+
use Illuminate\Database\Eloquent\SoftDeletes;
10+
11+
class Link extends Model
12+
{
13+
use SoftDeletes;
14+
15+
protected $fillable = [
16+
'link',
17+
'description',
18+
'author_name',
19+
'author_email',
20+
'cover_image',
21+
];
22+
23+
public function tags(): BelongsToMany
24+
{
25+
return $this->belongsToMany(Tag::class);
26+
}
27+
28+
public function scopeApproved(Builder $query): Builder
29+
{
30+
return $query->whereNotNull('approved_at');
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Domains\Links\Requests;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class LinkStoreRequest extends FormRequest
8+
{
9+
public function authorize(): bool
10+
{
11+
return true;
12+
}
13+
14+
public function rules(): array
15+
{
16+
return [
17+
'link' => ['required', 'string'],
18+
'description' => ['required', 'string'],
19+
'author_name' => ['required', 'string'],
20+
'author_email' => ['required', 'email'],
21+
'cover_image' => ['required', 'image'],
22+
'tags' => ['required', 'array'],
23+
'tags.*.id' => ['required', 'integer', 'exists:tags'],
24+
];
25+
}
26+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Domains\Links\Resources;
4+
5+
use Domains\Tags\Resources\TagResource;
6+
use Illuminate\Http\Resources\Json\JsonResource;
7+
8+
class LinkResource extends JsonResource
9+
{
10+
public function toArray($request): array
11+
{
12+
return [
13+
'id' => $this->id,
14+
'link' => $this->link,
15+
'description' => $this->description,
16+
'author_name' => $this->author_name,
17+
'author_email' => $this->author_email,
18+
'cover_image' => $this->cover_image,
19+
'created_at' => $this->created_at,
20+
'updated_at' => $this->updated_at,
21+
'approved_at' => $this->approved_at,
22+
'tags' => TagResource::collection(
23+
$this->whenLoaded('tags')
24+
),
25+
];
26+
}
27+
}

domains/Links/Routes/api.php

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
use Domains\Links\Controllers\LinksIndexController;
4+
use Domains\Links\Controllers\LinksStoreController;
5+
6+
Route::get('/links', LinksIndexController::class)->name('links.index');
7+
Route::post('/links', LinksStoreController::class)->name('links.store');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace Domains\Links\Tests\Feature;
4+
5+
use Domains\Links\Models\Link;
6+
use Illuminate\Foundation\Testing\RefreshDatabase;
7+
use Tests\TestCase;
8+
9+
class LinksIndexTest extends TestCase
10+
{
11+
use RefreshDatabase;
12+
13+
protected function setUp(): void
14+
{
15+
parent::setUp();
16+
17+
factory(Link::class, 20)->state('approved')->create();
18+
}
19+
20+
/** @test */
21+
public function it_lists_resources(): void
22+
{
23+
$this->getJson('/links')
24+
->assertSuccessful()
25+
->assertJsonStructure([
26+
'data' => [
27+
[
28+
'id', 'link', 'description', 'cover_image', 'author_name', 'author_email', 'created_at',
29+
],
30+
],
31+
'links' => [
32+
'first', 'last', 'prev', 'next',
33+
],
34+
]);
35+
}
36+
37+
/** @test */
38+
public function it_includes_tags_relation(): void
39+
{
40+
$this->getJson('/links?include=tags')
41+
->assertSuccessful()
42+
->assertJsonStructure([
43+
'data' => [
44+
[
45+
'tags',
46+
],
47+
],
48+
])
49+
->assertJsonCount(1, 'data.0.tags');
50+
}
51+
52+
/** @test */
53+
public function it_doesnt_include_relations_if_not_required(): void
54+
{
55+
$response = $this->getJson('/links')
56+
->assertSuccessful()
57+
->decodeResponseJson();
58+
59+
$this->assertArrayNotHasKey('tags', $response['data'][0]);
60+
}
61+
62+
/** @test */
63+
public function it_supports_pagination_navigation(): void
64+
{
65+
$this->getJson('/links?page=2')
66+
->assertSuccessful()
67+
->assertJsonPath('meta.current_page', 2);
68+
}
69+
}

0 commit comments

Comments
 (0)