now users can be followed through the profile page

This commit is contained in:
Ghostie 2024-12-28 20:07:56 -05:00
parent 1f3c063ec5
commit 0d14d08246
12 changed files with 243 additions and 17 deletions

View File

@ -17,6 +17,8 @@ Notice that the styles were taken from [AnySpace](https://anyspace.3to.moe/about
- [ ] Local posts should be federated
- [ ] Remote posts should be fetched
- [x] Follows
- [ ] I cannot follow myself
- [ ] Check when waiting for approval
- [ ] Likes
- [ ] Comments
@ -25,7 +27,7 @@ Notice that the styles were taken from [AnySpace](https://anyspace.3to.moe/about
- [ ] Show when the user is online
- [ ] Set mood
- [ ] Set interests
- [ ] Update profile picture
- [x] Update profile picture
- [ ] Mark account as private (in federation manual approval is needed)
- [ ] Allow custom CSS
- [ ] Profile audio

View File

@ -0,0 +1,60 @@
<?php
namespace App\Actions;
use GuzzleHttp\Client;
use App\Types\TypeActor;
use Illuminate\Support\Facades\Log;
class ActionsFriends {
public static function add_friend ($target)
{
if (!auth ()->check ())
return ["error" => "You must be logged in to add friends."];
$target_actor = TypeActor::actor_exists_or_obtain ($target);
try {
$client = new Client ();
$response = $client->post (auth ()->user ()->actor->outbox, [
"json" => [
"type" => "Follow",
"object" => $target
]
]);
}
catch (\Exception $e)
{
Log::error ("Error adding friend: " . $e->getMessage ());
return ["error" => "Error adding friend"];
}
return ["success" => "Friend added"];
}
public static function remove_friend ($target)
{
if (!auth ()->check ())
return ["error" => "You must be logged in to remove friends."];
$target_actor = TypeActor::actor_exists_or_obtain ($target);
try {
$client = new Client ();
$response = $client->post (auth ()->user ()->actor->outbox, [
"json" => [
"type" => "Unfollow",
"object" => $target
]
]);
}
catch (\Exception $e)
{
Log::error ("Error removing friend: " . $e->getMessage ());
return ["error" => "Error removing friend"];
}
return ["success" => "Friend removed"];
}
}

View File

@ -4,6 +4,9 @@ namespace App\Http\Controllers\AP;
use App\Models\User;
use App\Models\Actor;
use App\Models\Activity;
use App\Types\TypeActivity;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
@ -11,9 +14,65 @@ use App\Http\Controllers\Controller;
class APOutboxController extends Controller
{
public function outbox (User $user)
public function outbox (User $user, Request $request)
{
switch ($request->get ("type"))
{
case "Follow":
return $this->handle_follow ($user, $request->get ("object"));
break;
case "Unfollow":
return $this->handle_unfollow ($user, $request->get ("object"));
break;
default:
Log::info ("APOutboxController@index");
Log::info (json_encode (request ()->all ()));
break;
}
}
public function handle_follow (User $user, string $object)
{
$object_actor = Actor::where ("actor_id", $object)->first ();
if (!$object_actor)
return response ()->json ([ "error" => "object not found" ], 404);
$follow_activity = TypeActivity::craft_follow ($user->actor ()->first (), $object_actor);
$response = TypeActivity::post_activity ($follow_activity, $user->actor ()->first (), $object_actor);
if ($response->getStatusCode () < 200 || $response->getStatusCode () >= 300)
return response ()->json ([ "error" => "failed to post activity" ], 500);
return [
"success" => "followed"
];
}
public function handle_unfollow (User $user, string $object)
{
$object_actor = Actor::where ("actor_id", $object)->first ();
if (!$object_actor)
return response ()->json ([ "error" => "object not found" ], 404);
$object_id = '"' . str_replace ("/", "\/", $object_actor->actor_id) . '"';
$follow_activity = Activity::where ("actor", $user->actor ()->first ()->actor_id)
->where ("object", $object_id)
->where ("type", "Follow")
->first ();
if (!$follow_activity)
return response ()->json ([ "error" => "no follow activity found" ], 404);
$unfollow_activity = TypeActivity::craft_undo ($follow_activity, $user->actor ()->first ());
$response = TypeActivity::post_activity ($unfollow_activity, $user->actor ()->first (), $object_actor);
if ($response->getStatusCode () < 200 || $response->getStatusCode () >= 300)
return response ()->json ([ "error" => "failed to post activity" ], 500);
$follow_activity->delete ();
return [
"success" => "unfollowed"
];
}
}

View File

@ -3,10 +3,13 @@
namespace App\Http\Controllers;
use App\Types\TypeActor;
use App\Actions\ActionsFriends;
use App\Models\User;
use App\Models\Actor;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
class HomeController extends Controller
@ -48,7 +51,7 @@ class HomeController extends Controller
foreach ($user->friend_requests () as $request)
{
$actor = Actor::where ("actor_id", $request->actor)->first ();
$actor = Actor::where ("actor_id", $request)->first ();
if (!$actor)
continue;
@ -57,4 +60,22 @@ class HomeController extends Controller
return view ("users.requests", compact ("user", "requests"));
}
public function requests_accept (Request $request)
{
$user = auth ()->user ();
if (isset ($request->accept))
{
// accept a single request
$target = $request->accept;
$action = ActionsFriends::add_friend ($target);
if (isset ($action ["error"]))
{
return back ()->with ("error", $action ["error"]);
}
return back ()->with ("success", $action ["success"]);
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Actions\ActionsFriends;
class UserActionController extends Controller
{
public function friend (Request $request)
{
$response = ActionsFriends::add_friend ($request->get ("object"));
if (isset ($response ["error"]))
return back ()->with ("error", $response ["error"]);
return back ()->with ("success", $response ["success"]);
}
public function unfriend (Request $request)
{
$response = ActionsFriends::remove_friend ($request->get ("object"));
if (isset ($response ["error"]))
return back ()->with ("error", $response ["error"]);
return back ()->with ("success", $response ["success"]);
}
}

View File

@ -3,6 +3,8 @@
namespace App\Models;
use App\Models\User;
use App\Models\Activity;
use App\Types\TypeActor;
use Illuminate\Database\Eloquent\Model;
@ -52,4 +54,15 @@ class Actor extends Model
{
return TypeActor::build_response ($actor);
}
public function friends_with (Actor $actor)
{
$self_id = '"' . str_replace ("/", "\/", $this->actor_id) . '"';
$other_id = '"' . str_replace ("/", "\/", $actor->actor_id) . '"';
$following = Activity::where ("actor", $this->actor_id)->where ("type", "Follow")->where ("object", $other_id)->first ();
$followers = Activity::where ("actor", $actor->actor_id)->where ("type", "Follow")->where ("object", $self_id)->first ();
return $following && $followers;
}
}

View File

@ -77,19 +77,19 @@ class User extends Authenticatable
{
$actor_id = '"' . str_replace ("/", "\/", $this->actor->actor_id) . '"';
$followers = Activity::where ("type", "Follow")->where ("object", $actor_id)->get ();
$following = Activity::where ("type", "Follow")->where ("actor", $actor_id)->get ();
$followers = Activity::where ("type", "Follow")->where ("object", $actor_id)->pluck ("actor")->toArray ();
$following = Activity::where ("type", "Follow")->where ("actor", $this->actor->actor_id)->pluck ("object")->toArray ();
return $followers->intersect ($following);
return array_intersect ($followers, $following);
}
public function friend_requests ()
{
$actor_id = '"' . str_replace ("/", "\/", $this->actor->actor_id) . '"';
$followers = Activity::where ("type", "Follow")->where ("object", $actor_id)->get ();
$following = Activity::where ("type", "Follow")->where ("actor", $actor_id)->get ();
$followers = Activity::where ("type", "Follow")->where ("object", $actor_id)->pluck ("actor")->toArray ();
$following = Activity::where ("type", "Follow")->where ("actor", $this->actor->actor_id)->pluck ("object")->toArray ();
return $followers->diff ($following);
return array_diff ($followers, $following);
}
}

View File

@ -45,6 +45,30 @@ class TypeActivity {
return $accept_activity;
}
public static function craft_undo (Activity $activity, Actor $self)
{
$undo_activity = new Activity ();
$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->save ();
return $undo_activity;
}
public static function craft_follow (Actor $actor, Actor $object)
{
$follow_activity = new Activity ();
$follow_activity->activity_id = env ("APP_URL") . "/activity/" . uniqid ();
$follow_activity->type = "Follow";
$follow_activity->actor = $actor->actor_id;
$follow_activity->object = $object->actor_id;
$follow_activity->save ();
return $follow_activity;
}
public static function craft_signed_headers ($activity, Actor $source, Actor $target)
{
if (!$source->user)

View File

@ -46,12 +46,23 @@
<h4>Contacting {{ $actor->preferredUsername }}</h4>
</div>
@auth
<div class="inner">
<div class="f-row">
<div class="f-col">
<a href="#">
@if (auth ()->user ()->actor->friends_with ($actor))
<form action="{{ route ('user.unfriend') }}" onclick="this.submit ()" method="post">
@csrf
<input type="hidden" name="object" value="{{ $actor->actor_id }}">
<img loading="lazy" src="/resources/icons/delete.png" alt=""> Remove Friend
</form>
@else
<form action="{{ route ('user.friend') }}" onclick="this.submit ()" method="post">
@csrf
<input type="hidden" name="object" value="{{ $actor->actor_id }}">
<img loading="lazy" src="/resources/icons/add.png" alt=""> Add to Friends
</a>
</form>
@endif
</div>
<div class="f-col">
@ -103,6 +114,7 @@
</div>
</div>
</div>
@endauth
</div>
<div class="url-info">

View File

@ -43,9 +43,10 @@
<p>
<b>Friend Request</b>
</p>
<form action="#" method="POST">
<form method="POST">
@csrf
<input type="submit" name="accept" value="Accept">
<input type="hidden" name="accept" value="{{ $frequest->actor_id }}">
<input type="submit" name="submit" value="Accept">
</form>
</td>
</tr>

View File

@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\UserActionController;
Route::get('/', [ HomeController::class, "home" ])->name ("home");
@ -15,6 +16,10 @@ Route::get ("/auth/logout", [ UserController::class, "logout" ])->name ("logout"
Route::post ("/auth/signup", [ UserController::class, "do_signup" ])->middleware ("guest");
Route::post ("/auth/login", [ UserController::class, "do_login" ])->middleware ("guest");
// user actions
Route::post ("/user/action/friend", [ UserActionController::class, "friend" ])->name ("user.friend")->middleware ("auth");
Route::post ("/user/action/unfriend", [ UserActionController::class, "unfriend" ])->name ("user.unfriend")->middleware ("auth");
// user routes
Route::get ("/user/edit", [ ProfileController::class, "edit" ])->name ("users.edit")->middleware ("auth");
Route::post ("/user/edit", [ ProfileController::class, "update" ])->middleware ("auth");
@ -23,5 +28,6 @@ Route::get ("/user/{user_name}", [ ProfileController::class, "show" ])->name ("u
// other routes
Route::get ("/search", [ HomeController::class, "search" ])->name ("search");
Route::get ("/requests", [ HomeController::class, "requests" ])->name ("requests")->middleware ("auth");
Route::post ("/requests", [ HomeController::class, "requests_accept" ])->middleware ("auth");
require __DIR__ . "/api.php";