added like functionality

This commit is contained in:
Ghostie 2025-01-05 10:59:30 -05:00
parent d32c81575f
commit b65dedae06
13 changed files with 217 additions and 10 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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 ();

View File

@ -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 ();

View File

@ -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 ();
}
}

29
app/Models/Like.php Normal file
View File

@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Like extends Model
{
protected $fillable = [
"activity_id",
"note_id",
"actor_id"
];
public function get_activity ()
{
return $this->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");
}
}

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('likes', function (Blueprint $table) {
$table->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');
}
};

View File

@ -31,6 +31,8 @@ else
</b>
</p>
<h4>{{ $post->summary }}</h4>
{!! $post->content !!}
<p>
@ -42,6 +44,10 @@ else
<br>
<hr>
<p>
<span><b>Likes:</b> {{ $post->get_likes ()->count () }}</span>
</p>
<a href="{{ route ('posts.show', [ 'note' => $post ]) }}">
<button type="button">View</button>
</a>

View File

@ -62,6 +62,17 @@
<br>
<div class="buttons">
<form action="{{ route ('posts.like', [ 'note' => $note->id ]) }}" method="POST">
@csrf
<button type="submit">{{ auth ()->user ()->actor ()->first ()->liked_note ($note) ? "Undo Like" : "Like" }}</button>
</form>
</div>
<p>
<b>Likes</b>: {{ $note->get_likes ()->count () }}
</p>
<div class="comments" id="comments">
<div class="heading">
<h4>Comments</h4>

View File

@ -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");