Skip to content

Commit fbe9889

Browse files
authored
Merge pull request #57 from BinaryStudioAcademy/feature/likes-entity
Feature/likes entity
2 parents 571fe8f + e0c6c00 commit fbe9889

File tree

17 files changed

+303
-37
lines changed

17 files changed

+303
-37
lines changed

backend/app/Entity/Comment.php

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
namespace App\Entity;
66

77
use Carbon\Carbon;
8-
use Illuminate\Database\Eloquent\Builder;
98
use Illuminate\Database\Eloquent\Model;
109
use Illuminate\Database\Eloquent\Relations\BelongsTo;
10+
use InvalidArgumentException;
1111

1212
/**
1313
* Class Comment
@@ -29,18 +29,8 @@ final class Comment extends Model
2929
'tweet_id'
3030
];
3131

32-
protected static function boot()
33-
{
34-
parent::boot();
35-
36-
// append author relation in entity by default
37-
self::addGlobalScope(
38-
'with-author',
39-
function(Builder $builder) {
40-
$builder->with('author');
41-
}
42-
);
43-
}
32+
// append author relation in entity by default
33+
protected $with = ['author'];
4434

4535
public function author(): BelongsTo
4636
{
@@ -80,7 +70,7 @@ public function getAuthorId(): int
8070
public function edit(string $text): void
8171
{
8272
if (empty($text)) {
83-
throw new \InvalidArgumentException('Comment body cannot be empty.');
73+
throw new InvalidArgumentException('Comment body cannot be empty.');
8474
}
8575

8676
$this->body = $text;

backend/app/Entity/Like.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Entity;
6+
7+
use Carbon\Carbon;
8+
use Illuminate\Database\Eloquent\Model;
9+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
10+
use Illuminate\Database\Eloquent\Relations\MorphTo;
11+
12+
/**
13+
* Class Like
14+
*
15+
* Polymorphic entity
16+
*
17+
* @package App\Entity
18+
*
19+
* @property int $id
20+
* @property int $user_id
21+
* @property User $user
22+
* @property int $likeable_id
23+
* @property string $likeable_type
24+
* @property Carbon $created_at
25+
*/
26+
final class Like extends Model
27+
{
28+
protected $table = 'likes';
29+
30+
protected $fillable = [
31+
'user_id',
32+
'likeable_id',
33+
'likeable_type'
34+
];
35+
36+
protected $with = ['user'];
37+
38+
public function likeable(): MorphTo
39+
{
40+
return $this->morphTo();
41+
}
42+
43+
public function user(): BelongsTo
44+
{
45+
return $this->belongsTo(User::class);
46+
}
47+
48+
public function getId(): int
49+
{
50+
return $this->id;
51+
}
52+
53+
public function getCreatedAt(): Carbon
54+
{
55+
return $this->created_at;
56+
}
57+
58+
public function getUserId(): int
59+
{
60+
return $this->user_id;
61+
}
62+
63+
public function getUser(): User
64+
{
65+
return $this->user;
66+
}
67+
}

backend/app/Entity/Tweet.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Database\Eloquent\Model;
99
use Illuminate\Database\Eloquent\Relations\BelongsTo;
1010
use Illuminate\Database\Eloquent\Relations\HasMany;
11+
use Illuminate\Database\Eloquent\Relations\MorphMany;
1112
use InvalidArgumentException;
1213

1314
/**
@@ -20,6 +21,7 @@
2021
* @property Carbon $created_at
2122
* @property Carbon $updated_at
2223
* @property int $comments_count
24+
* @property int $likes_count
2325
* @property User $author
2426
*/
2527
final class Tweet extends Model
@@ -30,7 +32,7 @@ final class Tweet extends Model
3032
protected $with = ['author'];
3133

3234
// Eager load related comments count each time.
33-
protected $withCount = ['comments'];
35+
protected $withCount = ['comments', 'likes'];
3436

3537
protected $fillable = [
3638
'text',
@@ -48,6 +50,11 @@ public function comments(): HasMany
4850
return $this->hasMany(Comment::class);
4951
}
5052

53+
public function likes(): MorphMany
54+
{
55+
return $this->morphMany(Like::class, 'likeable');
56+
}
57+
5158
public function getId(): int
5259
{
5360
return $this->id;
@@ -84,6 +91,11 @@ public function getCommentsCount(): int
8491
return (int)$this->comments_count;
8592
}
8693

94+
public function getLikesCount(): int
95+
{
96+
return (int)$this->likes_count;
97+
}
98+
8799
public function changeContent(string $text): void
88100
{
89101
if (empty($text)) {

backend/app/Http/Presenter/Tweet/TweetArrayPresenter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public function present(Tweet $tweet): array
2626
'image_url' => $tweet->getImageUrl(),
2727
'created_at' => $tweet->getCreatedAt()->toDateTimeString(),
2828
'author' => $this->userPresenter->present($tweet->getAuthor()),
29-
'comments_count' => $tweet->getCommentsCount()
29+
'comments_count' => $tweet->getCommentsCount(),
30+
'likes_count' => $tweet->getLikesCount()
3031
];
3132
}
3233

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Schema;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Database\Migrations\Migration;
6+
7+
class LikesTableMigration extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::create('likes', function(Blueprint $table) {
17+
$table->bigIncrements('id');
18+
$table->unsignedBigInteger('user_id');
19+
$table->unsignedBigInteger('likeable_id');
20+
$table->string('likeable_type');
21+
$table->dateTime('created_at');
22+
23+
$table
24+
->foreign('user_id')
25+
->references('id')
26+
->on('users')
27+
->onDelete('cascade');
28+
});
29+
}
30+
31+
/**
32+
* Reverse the migrations.
33+
*
34+
* @return void
35+
*/
36+
public function down()
37+
{
38+
Schema::dropIfExists('likes');
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Schema;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Database\Migrations\Migration;
6+
7+
class LikeIsUniqueForUser extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
// user can like smth only once
17+
Schema::table('likes', function(Blueprint $blueprint) {
18+
$blueprint->unique(['user_id', 'likeable_id', 'likeable_type']);
19+
});
20+
}
21+
22+
/**
23+
* Reverse the migrations.
24+
*
25+
* @return void
26+
*/
27+
public function down()
28+
{
29+
Schema::table('likes', function(Blueprint $blueprint) {
30+
// need to drop foreign key before we can drop unique index
31+
$blueprint->dropForeign('likes_user_id_foreign');
32+
$blueprint->dropUnique(['user_id', 'likeable_id', 'likeable_type']);
33+
$blueprint
34+
->foreign('user_id')
35+
->references('id')
36+
->on('users')
37+
->onDelete('cascade');
38+
});
39+
}
40+
}

backend/database/seeds/DatabaseSeeder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ public function run()
1414
$this->call(UserTableSeeder::class);
1515
$this->call(TweetTableSeeder::class);
1616
$this->call(CommentTableSeeder::class);
17+
$this->call(LikeTableSeeder::class);
1718
}
1819
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
use App\Entity\Comment;
4+
use App\Entity\Like;
5+
use App\Entity\Tweet;
6+
use App\Entity\User;
7+
use Carbon\Carbon;
8+
use Illuminate\Database\Seeder;
9+
use Illuminate\Support\Facades\DB;
10+
11+
class LikeTableSeeder extends Seeder
12+
{
13+
private const LIKES_COUNT = 10;
14+
15+
/**
16+
* Run the database seeds.
17+
*
18+
* @return void
19+
*/
20+
public function run()
21+
{
22+
$tweets = Tweet::all();
23+
$users = User::all();
24+
$comments = Comment::all();
25+
$now = Carbon::now();
26+
27+
$tweetLikes = $tweets->map(
28+
function(Tweet $tweet) use ($users, $now) {
29+
$userIds = $users->shuffle()->shuffle()->take(self::LIKES_COUNT)->pluck('id');
30+
31+
return $userIds->map(
32+
function(int $userId) use ($tweet, $now) {
33+
return [
34+
'user_id' => $userId,
35+
'likeable_id' => $tweet->id,
36+
'likeable_type' => Tweet::class,
37+
'created_at' => $now->toDateTimeString()
38+
];
39+
}
40+
);
41+
}
42+
);
43+
44+
$commentLikes = $comments->map(
45+
function(Comment $comment) use ($users, $now) {
46+
$userIds = $users->shuffle()->shuffle()->take(self::LIKES_COUNT)->pluck('id');
47+
48+
return $userIds->map(
49+
function(int $userId) use ($comment, $now) {
50+
return [
51+
'user_id' => $userId,
52+
'likeable_id' => $comment->id,
53+
'likeable_type' => Comment::class,
54+
'created_at' => $now->toDateTimeString()
55+
];
56+
}
57+
);
58+
}
59+
);
60+
61+
DB::table('likes')->insert(
62+
$tweetLikes
63+
->flatten(1)
64+
->merge($commentLikes->flatten(1))
65+
->toArray()
66+
);
67+
}
68+
}

backend/tests/Feature/Api/ApiTestCase.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ abstract class ApiTestCase extends BaseTestCase
4646
'text',
4747
'image_url',
4848
'author' => self::USER_RESOURCE_STRUCTURE,
49-
'comments_count'
49+
'comments_count',
50+
'likes_count'
5051
];
5152

5253
/**
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<template>
2+
<section class="hero is-info" v-show="show">
3+
<div class="hero-body">
4+
<div class="has-text-centered">
5+
<p class="title">
6+
{{ title }}
7+
</p>
8+
</div>
9+
</div>
10+
</section>
11+
</template>
12+
13+
<script>
14+
export default {
15+
name: 'NoContent',
16+
17+
props: {
18+
title: {
19+
default: 'No content :)',
20+
type: String
21+
},
22+
23+
show: {
24+
type: Boolean,
25+
required: true
26+
}
27+
}
28+
};
29+
</script>
30+
31+
<style scoped>
32+
33+
</style>

0 commit comments

Comments
 (0)