now local users can post as well

This commit is contained in:
Ghostie 2024-12-29 19:59:00 -05:00
parent 66da68f5c8
commit dcfa475a01
12 changed files with 269 additions and 5 deletions

View File

@ -0,0 +1,64 @@
<?php
namespace App\Actions;
use GuzzleHttp\Client;
use Illuminate\Support\Str;
use App\Models\Actor;
use App\Models\Activity;
use App\Models\Note;
use App\Models\NoteAttachment;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
class ActionsPost
{
public static function post_new ($request)
{
if (!auth ()->check ())
return ["error" => "You must be logged in to post."];
$processed_content = Str::markdown ($request->get ("content"));
$attachments = [];
if ($request->hasFile ("files"))
{
$files = $request->file ("files");
foreach ($files as $file)
{
$manager = new ImageManager (new Driver ());
$image = $manager->read ($file);
$image_data = $image->toJpeg ();
$fname = $file->hashName () . uniqid () . ".jpg";
Storage::disk ("public")->put ("images/" . $fname, $image_data);
$attachments[] = env ("APP_URL") . "/storage/images/" . $fname;
}
}
$actor = auth ()->user ()->actor ()->first ();
try {
$client = new Client ();
$response = $client->post ($actor->outbox, [
"json" => [
"type" => "Post",
"content" => $processed_content,
"attachments" => $attachments,
]
]);
}
catch (\Exception $e)
{
return ["error" => "Could not connect to server."];
}
return ["success" => "Post created"];
}
}

View File

@ -69,10 +69,17 @@ class APInboxController extends Controller
$actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]);
$child_activity = $activity ["object"];
if (!TypeActivity::activity_exists ($child_activity ["id"]))
$child_activity_id = "";
if (is_array ($child_activity))
$child_activity_id = $child_activity ["id"];
else
$child_activity_id = $child_activity;
if (!TypeActivity::activity_exists ($child_activity_id))
return response ()->json (["error" => "Child activity not found",], 404);
$child_activity = Activity::where ("activity_id", $child_activity ["id"])->first ();
$child_activity = Activity::where ("activity_id", $child_activity_id)->first ();
$child_activity->delete ();
// TODO: Should Undo create a new activity in database?

View File

@ -41,6 +41,9 @@ class APInstanceInboxController extends Controller
break;
}
Log::info ("APInstanceInboxController:inbox");
Log::info ($activity);
return response ()->json (["status" => "ok"]);
}

View File

@ -6,9 +6,12 @@ use App\Models\User;
use App\Models\Actor;
use App\Models\Activity;
use App\Models\Instance;
use App\Models\Note;
use App\Models\NoteAttachment;
use App\Types\TypeActivity;
use App\Types\TypeActor;
use App\Types\TypeNote;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
@ -33,6 +36,10 @@ class APOutboxController extends Controller
return $this->handle_unfollow ($user, $request->get ("object"));
break;
case "Post":
return $this->handle_post ($user, $request);
break;
default:
Log::info ("APOutboxController@index");
Log::info (json_encode (request ()->all ()));
@ -109,4 +116,35 @@ class APOutboxController extends Controller
"success" => "unfollowed"
];
}
public function handle_post (User $user, $request)
{
$actor = $user->actor ()->first ();
$note = TypeNote::craft_from_outbox ($actor, $request);
if (isset ($request ["attachments"]))
{
foreach ($request ["attachments"] as $attachment)
{
$attachment_note = NoteAttachment::create ([
"note_id" => $note->id,
"url" => $attachment
]);
}
}
$create_activity = TypeActivity::craft_create ($actor, $note);
$note->activity_id = $create_activity->id;
$note->save ();
$instances = Instance::all ();
foreach ($instances as $instance)
{
$response = TypeActivity::post_activity ($create_activity, $actor, $instance->inbox);
if ($response->getStatusCode () < 200 || $response->getStatusCode () >= 300)
continue;
}
}
}

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Actions\ActionsFriends;
use App\Actions\ActionsPost;
class UserActionController extends Controller
{
@ -25,4 +26,18 @@ class UserActionController extends Controller
return back ()->with ("success", $response ["success"]);
}
public function post_new (Request $request)
{
$request->validate ([
"content" => "required",
"files.*" => "mimes:jpeg,png,jpg,gif,webm|max:4096"
]);
$response = ActionsPost::post_new ($request);
if (isset ($response ["error"]))
return back ()->with ("error", $response ["error"]);
return back ()->with ("success", $response ["success"]);
}
}

View File

@ -17,7 +17,7 @@ class Note extends Model
"url",
"attributedTo",
"content",
"tags",
"tag",
];
public function get_activity ()

View File

@ -81,6 +81,25 @@ class TypeActivity {
return $update_activity;
}
public static function craft_create (Actor $actor, $fields)
{
$create_activity = new Activity ();
$create_activity->activity_id = env ("APP_URL") . "/activity/" . uniqid ();
$create_activity->type = "Create";
$create_activity->actor = $actor->actor_id;
switch ($fields ["type"])
{
case "Note":
$create_activity->object = TypeNote::build_response ($fields);
break;
}
$create_activity->save ();
return $create_activity;
}
public static function craft_signed_headers ($activity, Actor $source, $target)
{
if (!$source->user)

View File

@ -103,6 +103,42 @@ class TypeActor {
]
];
if ($actor->user)
{
$response ["attachment"] = [
[
"type" => "PropertyValue",
"name" => "Interests General",
"value" => $actor->user->interests_general
],
[
"type" => "PropertyValue",
"name" => "Interests Music",
"value" => $actor->user->interests_music
],
[
"type" => "PropertyValue",
"name" => "Interests Movies",
"value" => $actor->user->interests_movies
],
[
"type" => "PropertyValue",
"name" => "Interests Television",
"value" => $actor->user->interests_television
],
[
"type" => "PropertyValue",
"name" => "Interests Books",
"value" => $actor->user->interests_books
],
[
"type" => "PropertyValue",
"name" => "Interests Heroes",
"value" => $actor->user->interests_heroes
]
];
}
return $response;
}

View File

@ -11,6 +11,58 @@ use Illuminate\Support\Facades\Log;
class TypeNote
{
public static function build_response (Note $note)
{
$author = $note->get_actor ()->first ();
$response = [
"id" => $note->note_id,
"type" => "Note",
"summary" => $note->summary,
"inReplyTo" => $note->in_reply_to,
"published" => $note->created_at,
"url" => $note->url,
"attributedTo" => $note->attributedTo,
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
$author->following
],
"content" => $note->content
];
$attachments = $note->attachments ()->get ();
foreach ($attachments as $attachment)
{
$response ["attachment"] [] = [
"type" => "Document",
"mediaType" => "image/jpeg",
"url" => $attachment->url
];
}
return $response;
}
public static function craft_from_outbox (Actor $actor, $request)
{
// TODO: url should be route ('posts.show', $note->id)
$note = Note::create ([
"actor_id" => $actor->id,
"note_id" => env ("APP_URL") . "/ap/v1/note/" . uniqid (),
"in_reply_to" => $request ["inReplyTo"] ?? null,
"type" => "Note",
"summary" => $request ["summary"] ?? null,
"url" => "TODO",
"attributedTo" => $actor->actor_id,
"content" => $request ["content"] ?? null,
"tag" => $request ["tag"] ?? null
]);
return $note;
}
public static function update_from_request (Note $note, $request, Activity $activity, Actor $actor)
{
$note->activity_id = $activity->id;

View File

@ -1,11 +1,20 @@
@php
$actor_url = "";
if ($actor->user_id)
$actor_url = route ('users.show', [ 'user_name' => $actor->user->name ]);
else
$actor_url = route ('users.show', [ 'user_name' => $actor->local_actor_id ]);
@endphp
<tr>
<td>
<a href="{{ route ('users.show', [ 'user_name' => $actor->local_actor_id ]) }}">
<a href="{{ $actor_url }}">
<p>
<b>{{ $actor->name }}</b>
</p>
</a>
<a href="{{ route ('users.show', [ 'user_name' => $actor->local_actor_id ]) }}">
<a href="{{ $actor_url }}">
<p>
@if ($actor->user)
<img loading="lazy" src="{{ $actor->user->avatar }}" class="pfp-fallback" width="50">

View File

@ -274,6 +274,26 @@
<b>{{ $actor->preferredUsername }} has <span class="count">{{ count ($actor->posts) }}</span> posts.</b>
</p>
@if (auth ()->user () && auth ()->user ()->is ($user))
<form action="{{ route ('user.post.new') }}" method="post" enctype="multipart/form-data">
@csrf
<textarea name="content" placeholder="What's on your mind?" cols="60" rows="5"></textarea>
<input type="file" name="files[]" accept="image/*" multiple>
<button type="submit">Post</button>
<small>Markdown is supported</small>
@error ("content")
<div class="error">{{ $message }}</div>
@enderror
@error ("files.*")
<div class="error">{{ $message }}</div>
@enderror
</form>
@endif
<br>
<table class="comments-table" cellspacing="0" cellpadding="3" bordercollor="#ffffff" border="1">
<tbody>
@foreach ($actor->posts as $post)

View File

@ -19,6 +19,7 @@ Route::post ("/auth/login", [ UserController::class, "do_login" ])->middleware (
// 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");
Route::post ("/user/action/post/new", [ UserActionController::class, "post_new" ])->name ("user.post.new")->middleware ("auth");
// user routes
Route::get ("/user/edit", [ ProfileController::class, "edit" ])->name ("users.edit")->middleware ("auth");