From b65dedae06e3b0bbc094dc84a58ccf7b280a4173 Mon Sep 17 00:00:00 2001
From: Ghostie
Date: Sun, 5 Jan 2025 10:59:30 -0500
Subject: [PATCH] added like functionality
---
README.md | 6 +--
app/Actions/ActionsPost.php | 21 ++++++++
app/Http/Controllers/AP/APInboxController.php | 49 ++++++++++++++++---
.../Controllers/AP/APOutboxController.php | 35 +++++++++++++
app/Http/Controllers/PostController.php | 13 +++++
app/Models/Actor.php | 5 ++
app/Models/Like.php | 29 +++++++++++
app/Models/Note.php | 5 ++
app/Types/TypeActivity.php | 14 +++++-
.../2025_01_04_184440_create_likes_table.php | 32 ++++++++++++
.../views/components/comment_block.blade.php | 6 +++
resources/views/posts/show.blade.php | 11 +++++
routes/web.php | 1 +
13 files changed, 217 insertions(+), 10 deletions(-)
create mode 100644 app/Models/Like.php
create mode 100644 database/migrations/2025_01_04_184440_create_likes_table.php
diff --git a/README.md b/README.md
index 1858cc5..fb4e47d 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Notice that the styles were taken from [AnySpace](https://anyspace.3to.moe/about
- [x] I cannot follow myself
- [ ] Check when waiting for approval
- [ ] Handle Rejection
- - [ ] Likes
+ - [x] Likes
- [ ] Comments
- [-] Social features
@@ -39,8 +39,8 @@ Notice that the styles were taken from [AnySpace](https://anyspace.3to.moe/about
- [x] Remove friends
- [x] Posts (everything should be federated)
- [x] Create posts
- - [ ] Delete posts
- - [ ] Like posts
+ - [x] Delete posts
+ - [x] Like posts
- [ ] Comment posts
- [ ] Boost posts
- [ ] Post tags
diff --git a/app/Actions/ActionsPost.php b/app/Actions/ActionsPost.php
index 8378b21..0be048b 100644
--- a/app/Actions/ActionsPost.php
+++ b/app/Actions/ActionsPost.php
@@ -89,4 +89,25 @@ class ActionsPost
return ["success" => "Post created"];
}
+
+ public static function like_post (Actor $actor, Note $note)
+ {
+ $client = new Client ();
+
+ try
+ {
+ $response = $client->post ($actor->outbox, [
+ "json" => [
+ "type" => "Like",
+ "object" => $note->note_id,
+ ]
+ ]);
+ }
+ catch (\Exception $e)
+ {
+ return ["error" => "Could not connect to server."];
+ }
+
+ return $response;
+ }
}
diff --git a/app/Http/Controllers/AP/APInboxController.php b/app/Http/Controllers/AP/APInboxController.php
index 04c0fa7..5750dee 100644
--- a/app/Http/Controllers/AP/APInboxController.php
+++ b/app/Http/Controllers/AP/APInboxController.php
@@ -6,6 +6,8 @@ use App\Models\User;
use App\Models\Actor;
use App\Models\Activity;
use App\Models\Follow;
+use App\Models\Note;
+use App\Models\Like;
use App\Types\TypeActor;
use App\Types\TypeActivity;
@@ -32,14 +34,20 @@ class APInboxController extends Controller
case "Undo":
$this->handle_undo ($user, $request->all ());
break;
+
+ case "Like":
+ $this->handle_like ($user, $request->all ());
+ break;
+
+ default:
+ Log::info ("APInboxController@index");
+ Log::info ("Unknown type: " . $type);
+ break;
}
}
private function handle_follow (User $user, $activity)
{
- if (TypeActivity::activity_exists ($activity ["id"]))
- return response ()->json (["error" => "Activity already exists",], 409);
-
$actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]);
$target = TypeActor::actor_get_local ($activity ["object"]);
@@ -77,9 +85,6 @@ class APInboxController extends Controller
public function handle_undo (User $user, $activity)
{
- if (TypeActivity::activity_exists ($activity ["id"]))
- return response ()->json (["error" => "Activity already exists",], 409);
-
$actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]);
$child_activity = $activity ["object"];
@@ -99,4 +104,36 @@ class APInboxController extends Controller
// TODO: Should Undo create a new activity in database?
return response ()->json (["success" => "Activity undone",], 200);
}
+
+ public function handle_like (User $user, $activity)
+ {
+ $actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]);
+ $note_id = $activity ["object"];
+ $note = Note::where ("note_id", $note_id)->first ();
+ if (!$note)
+ {
+ Log::info ("Note not found: " . $note_id);
+ return response ()->json (["error" => "Note not found",], 404);
+ }
+
+ // check like doesn't already exist
+ $like_exists = $actor->liked_note ($note);
+ if ($like_exists)
+ return response ()->json (["error" => "Like already exists",], 409);
+
+ $activity ["activity_id"] = $activity ["id"];
+ $activity_exists = TypeActivity::activity_exists ($activity ["id"]);
+ if (!$activity_exists)
+ $act = Activity::create ($activity);
+ else
+ $act = Activity::where ("activity_id", $activity ["id"])->first ();
+
+ $like = Like::create ([
+ "activity_id" => $act->id,
+ "actor_id" => $actor->id,
+ "note_id" => $note->id,
+ ]);
+
+ return response ()->json (["success" => "Like created",], 200);
+ }
}
diff --git a/app/Http/Controllers/AP/APOutboxController.php b/app/Http/Controllers/AP/APOutboxController.php
index 629f9ea..4fb5a7a 100644
--- a/app/Http/Controllers/AP/APOutboxController.php
+++ b/app/Http/Controllers/AP/APOutboxController.php
@@ -49,6 +49,10 @@ class APOutboxController extends Controller
return $this->handle_unfollow ($user, $request->get ("object"));
break;
+ case "Like":
+ return $this->handle_like ($user, $request->get ("object"));
+ break;
+
case "Post":
return $this->handle_post ($user, $request);
break;
@@ -200,6 +204,37 @@ class APOutboxController extends Controller
];
}
+ public function handle_like (User $user, $request)
+ {
+ $object = Note::where ("note_id", $request)->first ();
+ if (!$object)
+ return response ()->json ([ "error" => "object not found" ], 404);
+
+ $actor = $user->actor ()->first ();
+ $already_liked = $actor->liked_note ($object);
+ if ($already_liked)
+ {
+ // undo the like
+ $like_activity = $already_liked->get_activity ()->first ();
+ $undo_activity = TypeActivity::craft_undo ($like_activity, $actor);
+
+ $response = TypeActivity::post_activity ($undo_activity, $actor, $object->get_actor ()->first ());
+ return [
+ "success" => "unliked"
+ ];
+ }
+
+ $like_activity = TypeActivity::craft_like ($actor, $object->note_id);
+ $response = TypeActivity::post_activity ($like_activity, $actor, $object->get_actor ()->first ());
+
+ if ($response->getStatusCode () < 200 || $response->getStatusCode () >= 300)
+ return response ()->json ([ "error" => "failed to post activity" ], 500);
+
+ return [
+ "success" => "liked"
+ ];
+ }
+
public function handle_post (User $user, $request)
{
$actor = $user->actor ()->first ();
diff --git a/app/Http/Controllers/PostController.php b/app/Http/Controllers/PostController.php
index fc126f4..cef27d3 100644
--- a/app/Http/Controllers/PostController.php
+++ b/app/Http/Controllers/PostController.php
@@ -70,6 +70,19 @@ class PostController extends Controller
}
}
+ public function like (Note $note)
+ {
+ if (!auth ()->check ())
+ return back ()->with ("error", "You need to be logged in to like a post.");
+
+ $user = auth ()->user ();
+ $actor = $user->actor ()->first ();
+
+ $response = ActionsPost::like_post ($actor, $note);
+
+ return back ()->with ("success", "Post liked successfully.");
+ }
+
public function delete (Note $note)
{
$actor = auth ()->user ()->actor ()->first ();
diff --git a/app/Models/Actor.php b/app/Models/Actor.php
index df544d4..775c2b6 100644
--- a/app/Models/Actor.php
+++ b/app/Models/Actor.php
@@ -76,4 +76,9 @@ class Actor extends Model
return $following && $followers;
}
+
+ public function liked_note (Note $note)
+ {
+ return Like::where ("actor_id", $this->id)->where ("note_id", $note->id)->first ();
+ }
}
diff --git a/app/Models/Like.php b/app/Models/Like.php
new file mode 100644
index 0000000..fb48021
--- /dev/null
+++ b/app/Models/Like.php
@@ -0,0 +1,29 @@
+belongsTo (Activity::class, "activity_id");
+ }
+
+ public function get_note ()
+ {
+ return $this->belongsTo (Note::class, "note_id");
+ }
+
+ public function get_actor ()
+ {
+ return $this->belongsTo (Actor::class, "actor_id");
+ }
+}
diff --git a/app/Models/Note.php b/app/Models/Note.php
index a828c44..0079ceb 100644
--- a/app/Models/Note.php
+++ b/app/Models/Note.php
@@ -30,6 +30,11 @@ class Note extends Model
return $this->hasOne (Actor::class, "id", "actor_id");
}
+ public function get_likes ()
+ {
+ return $this->hasMany (Like::class);
+ }
+
public function attachments ()
{
return $this->hasMany (NoteAttachment::class);
diff --git a/app/Types/TypeActivity.php b/app/Types/TypeActivity.php
index f56d089..af3a397 100644
--- a/app/Types/TypeActivity.php
+++ b/app/Types/TypeActivity.php
@@ -53,7 +53,7 @@ class TypeActivity {
$undo_activity->activity_id = env ("APP_URL") . "/activity/" . uniqid ();
$undo_activity->type = "Undo";
$undo_activity->actor = $self->actor_id;
- $undo_activity->object = $activity;
+ $undo_activity->object = TypeActivity::craft_response ($activity);
$undo_activity->save ();
return $undo_activity;
@@ -119,6 +119,18 @@ class TypeActivity {
return $delete_activity;
}
+ public static function craft_like (Actor $actor, $id)
+ {
+ $like_activity = new Activity ();
+ $like_activity->activity_id = env ("APP_URL") . "/activity/" . uniqid ();
+ $like_activity->type = "Like";
+ $like_activity->actor = $actor->actor_id;
+ $like_activity->object = $id;
+ $like_activity->save ();
+
+ return $like_activity;
+ }
+
public static function get_private_key (Actor $actor)
{
return openssl_get_privatekey ($actor->private_key);
diff --git a/database/migrations/2025_01_04_184440_create_likes_table.php b/database/migrations/2025_01_04_184440_create_likes_table.php
new file mode 100644
index 0000000..a4ed35a
--- /dev/null
+++ b/database/migrations/2025_01_04_184440_create_likes_table.php
@@ -0,0 +1,32 @@
+id();
+
+ $table->foreignId ("activity_id")->constrained ()->onDelete ("cascade");
+ $table->foreignId ("note_id")->constrained ()->onDelete ("cascade");
+ $table->foreignId ("actor_id")->constrained ()->onDelete ("cascade");
+
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('likes');
+ }
+};
diff --git a/resources/views/components/comment_block.blade.php b/resources/views/components/comment_block.blade.php
index 363fe44..f2ac51b 100644
--- a/resources/views/components/comment_block.blade.php
+++ b/resources/views/components/comment_block.blade.php
@@ -31,6 +31,8 @@ else
+ {{ $post->summary }}
+
{!! $post->content !!}
@@ -42,6 +44,10 @@ else
+
+ Likes: {{ $post->get_likes ()->count () }}
+
+
diff --git a/resources/views/posts/show.blade.php b/resources/views/posts/show.blade.php
index 0f38d62..42f8ac2 100644
--- a/resources/views/posts/show.blade.php
+++ b/resources/views/posts/show.blade.php
@@ -62,6 +62,17 @@
+
+
+
+
+
+ Likes: {{ $note->get_likes ()->count () }}
+
+
Comments
diff --git a/routes/web.php b/routes/web.php index fc575d6..6ef296a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -31,6 +31,7 @@ Route::get ("/user/{user_name}", [ ProfileController::class, "show" ])->name ("u // posts routes Route::get ("/post/{note}/edit", [ PostController::class, "edit" ])->name ("posts.edit")->middleware ("auth"); Route::post ("/post/{note}/edit", [ PostController::class, "update" ])->middleware ("auth"); +Route::post ("/post/{note}/like", [ PostController::class, "like" ])->name ("posts.like")->middleware ("auth"); Route::get ("/post/{note}", [ PostController::class, "show" ])->name ("posts.show"); Route::delete ("/post/{note}", [ PostController::class, "delete" ])->name ("posts.delete")->middleware ("auth");