diff --git a/app/Actions/ActionsPost.php b/app/Actions/ActionsPost.php new file mode 100644 index 0000000..388d7ff --- /dev/null +++ b/app/Actions/ActionsPost.php @@ -0,0 +1,64 @@ +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"]; + } +} diff --git a/app/Http/Controllers/AP/APInboxController.php b/app/Http/Controllers/AP/APInboxController.php index 5260dac..5ee63bf 100644 --- a/app/Http/Controllers/AP/APInboxController.php +++ b/app/Http/Controllers/AP/APInboxController.php @@ -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? diff --git a/app/Http/Controllers/AP/APInstanceInboxController.php b/app/Http/Controllers/AP/APInstanceInboxController.php index be3d491..a08ef48 100644 --- a/app/Http/Controllers/AP/APInstanceInboxController.php +++ b/app/Http/Controllers/AP/APInstanceInboxController.php @@ -41,6 +41,9 @@ class APInstanceInboxController extends Controller break; } + Log::info ("APInstanceInboxController:inbox"); + Log::info ($activity); + return response ()->json (["status" => "ok"]); } diff --git a/app/Http/Controllers/AP/APOutboxController.php b/app/Http/Controllers/AP/APOutboxController.php index 42d91b9..396665b 100644 --- a/app/Http/Controllers/AP/APOutboxController.php +++ b/app/Http/Controllers/AP/APOutboxController.php @@ -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; + } + } } diff --git a/app/Http/Controllers/UserActionController.php b/app/Http/Controllers/UserActionController.php index e9a50a5..635f292 100644 --- a/app/Http/Controllers/UserActionController.php +++ b/app/Http/Controllers/UserActionController.php @@ -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"]); + } } diff --git a/app/Models/Note.php b/app/Models/Note.php index 54e8308..a828c44 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -17,7 +17,7 @@ class Note extends Model "url", "attributedTo", "content", - "tags", + "tag", ]; public function get_activity () diff --git a/app/Types/TypeActivity.php b/app/Types/TypeActivity.php index fddcc86..3fffb4f 100644 --- a/app/Types/TypeActivity.php +++ b/app/Types/TypeActivity.php @@ -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) diff --git a/app/Types/TypeActor.php b/app/Types/TypeActor.php index 75aca13..a3490be 100644 --- a/app/Types/TypeActor.php +++ b/app/Types/TypeActor.php @@ -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; } diff --git a/app/Types/TypeNote.php b/app/Types/TypeNote.php index 45fb2e8..f3ecb55 100644 --- a/app/Types/TypeNote.php +++ b/app/Types/TypeNote.php @@ -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; diff --git a/resources/views/components/comment_block.blade.php b/resources/views/components/comment_block.blade.php index f572de1..f4469eb 100644 --- a/resources/views/components/comment_block.blade.php +++ b/resources/views/components/comment_block.blade.php @@ -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 + - +

{{ $actor->name }}

- +

@if ($actor->user) diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index 3d4c4a4..5645d0d 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -274,6 +274,26 @@ {{ $actor->preferredUsername }} has {{ count ($actor->posts) }} posts.

+ @if (auth ()->user () && auth ()->user ()->is ($user)) +
+ @csrf + + + + Markdown is supported + + @error ("content") +
{{ $message }}
+ @enderror + + @error ("files.*") +
{{ $message }}
+ @enderror +
+ @endif + +
+ @foreach ($actor->posts as $post) diff --git a/routes/web.php b/routes/web.php index 3b7b125..9143332 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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");