+ + — by {{ $blog->user->name }} + — {{ count ($blog->notes) }} Posts +
+ + +diff --git a/README.md b/README.md index f86069b..0cb4353 100644 --- a/README.md +++ b/README.md @@ -195,11 +195,12 @@ Restart nginx: sudo systemctl restart nginx ``` -Now link the storage to the public folder and install the dependencies for reverb: +Now link the storage to the public folder, install the dependencies for reverb and seed the database: ```bash php artisan storage:link php artisan install:broadcasting +php artisan db:seed ``` Now, we need to create three services to handle the jobs that OurSpace needs to run, another to handle the notifications' queue and another one to run Laravel Reverb. So run something `emacs /lib/systemd/system/ourspace-queue.service`, `emacs /lib/systemd/system/ourspace-notifications.service`, `emacs /lib/systemd/system/ourspace-ws.service` and add the following content: diff --git a/app/Actions/ActionsPost.php b/app/Actions/ActionsPost.php index de61426..90cad87 100644 --- a/app/Actions/ActionsPost.php +++ b/app/Actions/ActionsPost.php @@ -115,7 +115,11 @@ class ActionsPost $processed = ActionsPost::process_content_and_attachments ($request); - $actor = auth ()->user ()->actor ()->first (); + $actor = null; + if ($request ["blog_id"]) + $actor = Actor::where ("blog_id", $request ["blog_id"])->first (); + else + $actor = auth ()->user ()->actor ()->first (); try { $client = new Client (); diff --git a/app/Events/BlogCreatedEvent.php b/app/Events/BlogCreatedEvent.php new file mode 100644 index 0000000..1280ada --- /dev/null +++ b/app/Events/BlogCreatedEvent.php @@ -0,0 +1,31 @@ +blog = $blog; + $this->user = $user; + } +} diff --git a/app/Http/Controllers/AP/APActorController.php b/app/Http/Controllers/AP/APActorController.php index 57a1ff8..00a2e38 100644 --- a/app/Http/Controllers/AP/APActorController.php +++ b/app/Http/Controllers/AP/APActorController.php @@ -17,25 +17,36 @@ use App\Http\Controllers\Controller; class APActorController extends Controller { - public function user (User $user) + public function user ($name) { + $actor = Actor::where ("preferredUsername", $name)->where ("user_id", "!=", null)->first (); + if (!$actor) + return response ()->json (["error" => "Actor not found"], 404)->header ("Content-Type", "application/activity+json"); + if (str_contains (request ()->header ("Accept"), "text/html")) { - return redirect (route ("users.show", ["user_name" => $user->name])); + if ($actor->blog_id) { + return redirect (route ("blogs.show", ["blog" => $actor->preferredUsername])); + } + + return redirect (route ("users.show", ["user_name" => $actor->preferredUsername])); } - $actor = $user->actor ()->get (); - $response = Actor::build_response ($actor->first ()); + $response = Actor::build_response ($actor); return response ()->json ($response)->header ("Content-Type", "application/activity+json"); } - public function followers (User $user) + public function followers ($name) { - $follower_ids = Follow::where ("object", $user->actor->id)->get (); + $actor = Actor::where ("preferredUsername", $name)->where ("user_id", "!=", null)->first (); + if (!$actor) + return response ()->json (["error" => "Actor not found"], 404)->header ("Content-Type", "application/activity+json"); + + $follower_ids = Follow::where ("object", $actor->id)->get (); $followers = Actor::whereIn ("id", $follower_ids->pluck ("actor")->toArray ()); $ordered_collection = new TypeOrderedCollection (); $ordered_collection->collection = $followers->get ()->pluck ("actor_id")->toArray (); - $ordered_collection->url = route ("ap.followers", $user->name); + $ordered_collection->url = route ("ap.followers", $actor->name); $ordered_collection->page_size = 10; if (request ()->has ("page")) { @@ -46,14 +57,18 @@ class APActorController extends Controller return response ()->json ($ordered_collection->build_response_main ())->header ("Content-Type", "application/activity+json"); } - public function following (User $user) + public function following ($name) { - $following_ids = Follow::where ("actor", $user->actor->id)->get (); + $actor = Actor::where ("preferredUsername", $name)->where ("user_id", "!=", null)->first (); + if (!$actor) + return response ()->json (["error" => "Actor not found"], 404)->header ("Content-Type", "application/activity+json"); + + $following_ids = Follow::where ("actor", $actor->id)->get (); $following = Actor::whereIn ("id", $following_ids->pluck ("object")->toArray ()); $ordered_collection = new TypeOrderedCollection (); $ordered_collection->collection = $following->get ()->pluck ("actor_id")->toArray (); - $ordered_collection->url = route ("ap.following", $user->name); + $ordered_collection->url = route ("ap.following", $actor->name); $ordered_collection->page_size = 10; if (request ()->has ("page")) { @@ -64,9 +79,13 @@ class APActorController extends Controller return response ()->json ($ordered_collection->build_response_main ())->header ("Content-Type", "application/activity+json"); } - public function featured (User $user) + public function featured ($name) { - $featured_ids = ProfilePin::where ("actor_id", $user->actor->id)->pluck ("note_id")->toArray (); + $actor = Actor::where ("preferredUsername", $name)->where ("user_id", "!=", null)->first (); + if (!$actor) + return response ()->json (["error" => "Actor not found"], 404)->header ("Content-Type", "application/activity+json"); + + $featured_ids = ProfilePin::where ("actor_id", $actor->id)->pluck ("note_id")->toArray (); $notes = Note::whereIn ("id", $featured_ids)->get (); $collection = []; @@ -77,7 +96,7 @@ class APActorController extends Controller $ordered_collection = new TypeOrderedCollection (); $ordered_collection->collection = $collection; - $ordered_collection->url = route ("ap.featured", $user->name); + $ordered_collection->url = route ("ap.featured", $actor->preferredUsername); $ordered_collection->page_size = 10; if (request ()->has ("page")) { diff --git a/app/Http/Controllers/AP/APInboxController.php b/app/Http/Controllers/AP/APInboxController.php index 0cdc084..2c7eae2 100644 --- a/app/Http/Controllers/AP/APInboxController.php +++ b/app/Http/Controllers/AP/APInboxController.php @@ -25,8 +25,12 @@ use App\Http\Controllers\Controller; class APInboxController extends Controller { - public function inbox (User $user) + public function inbox ($name) { + $actor = Actor::where ("preferredUsername", $name)->where ("user_id", "!=", null)->first (); + if (!$actor) + return response ()->json ([ "error" => "Actor not found" ], 404); + $request = request (); $type = $request->get ("type"); @@ -35,15 +39,15 @@ class APInboxController extends Controller switch ($type) { case "Follow": - $this->handle_follow ($user, $request->all ()); + $this->handle_follow ($actor, $request->all ()); break; case "Undo": - $this->handle_undo ($user, $request->all ()); + $this->handle_undo ($actor, $request->all ()); break; case "Like": - $this->handle_like ($user, $request->all ()); + $this->handle_like ($actor, $request->all ()); break; default: @@ -53,17 +57,17 @@ class APInboxController extends Controller } } - private function handle_follow (User $user, $activity) + private function handle_follow (Actor $actor, $activity) { ActivityFollowEvent::dispatch ($activity); } - public function handle_undo (User $user, $activity) + public function handle_undo (Actor $actor, $activity) { ActivityUndoEvent::dispatch ($activity); } - public function handle_like (User $user, $activity) + public function handle_like (Actor $actor, $activity) { ActivityLikeEvent::dispatch ($activity); } diff --git a/app/Http/Controllers/AP/APOutboxController.php b/app/Http/Controllers/AP/APOutboxController.php index 997a3c6..c7e5551 100644 --- a/app/Http/Controllers/AP/APOutboxController.php +++ b/app/Http/Controllers/AP/APOutboxController.php @@ -29,45 +29,47 @@ use Illuminate\Support\Facades\Storage; class APOutboxController extends Controller { - public function outbox (User $user, Request $request) + public function outbox ($name, Request $request) { + $actor = Actor::where ("preferredUsername", $name)->where ("user_id", "!=", null)->first (); + // TODO: check we are logged in and we are the logged in user switch ($request->get ("type")) { case "UpdateProfile": - return $this->handle_update_profile ($user); + return $this->handle_update_profile ($actor); break; case "UpdateNote": - return $this->handle_update_note ($user, $request); + return $this->handle_update_note ($actor, $request); break; case "DeleteNote": - return $this->handle_delete_note ($user, $request); + return $this->handle_delete_note ($actor, $request); break; case "Follow": - return $this->handle_follow ($user, $request->get ("object")); + return $this->handle_follow ($actor, $request->get ("object")); break; case "Unfollow": - return $this->handle_unfollow ($user, $request->get ("object")); + return $this->handle_unfollow ($actor, $request->get ("object")); break; case "Like": - return $this->handle_like ($user, $request->get ("object")); + return $this->handle_like ($actor, $request->get ("object")); break; case "Boost": - return $this->handle_boost ($user, $request->get ("object")); + return $this->handle_boost ($actor, $request->get ("object")); break; case "Pin": - return $this->handle_pin ($user, $request->get ("object")); + return $this->handle_pin ($actor, $request->get ("object")); break; case "Post": - return $this->handle_post ($user, $request); + return $this->handle_post ($actor, $request); break; default: @@ -77,9 +79,8 @@ class APOutboxController extends Controller } } - public function handle_update_profile (User $user) + public function handle_update_profile (Actor $actor) { - $actor = $user->actor ()->first (); $actor_response = TypeActor::build_response ($actor); $update_activity = TypeActivity::craft_update ($actor, $actor_response); @@ -87,10 +88,8 @@ class APOutboxController extends Controller return response ()->json ("success", 200); } - public function handle_update_note (User $user, $request) + public function handle_update_note (Actor $actor, $request) { - $actor = $user->actor ()->first (); - // first check if there are new attachments if ($request ["attachments"]) { @@ -135,9 +134,8 @@ class APOutboxController extends Controller return response ()->json ("success", 200); } - public function handle_delete_note (User $user, $request) + public function handle_delete_note (Actor $actor, $request) { - $actor = $user->actor ()->first (); $note = Note::where ("id", $request ["note"])->first (); if (!$note) return response ()->json ([ "error" => "note not found" ], 404); @@ -154,29 +152,29 @@ class APOutboxController extends Controller return response ()->json ("success", 200); } - public function handle_follow (User $user, string $object) + public function handle_follow (Actor $actor, string $object) { $object_actor = Actor::where ("actor_id", $object)->first (); if (!$object_actor) return response ()->json ([ "error" => "object not found" ], 404); - if ($user->actor ()->first ()->actor_id == $object_actor->actor_id) + if ($actor->actor_id == $object_actor->actor_id) return response ()->json ([ "error" => "cannot follow self" ], 400); // check we are not following already - $following_activity = Activity::where ("actor", $user->actor ()->first ()->actor_id) + $following_activity = Activity::where ("actor", $actor->actor_id) ->where ("object", '"' . str_replace ("/", "\/", $object_actor->actor_id) . '"') ->where ("type", "Follow") ->first (); if ($following_activity) return response ()->json ([ "error" => "already following" ], 400); - $follow_activity = TypeActivity::craft_follow ($user->actor ()->first (), $object_actor); - $response = TypeActivity::post_activity ($follow_activity, $user->actor ()->first (), $object_actor); + $follow_activity = TypeActivity::craft_follow ($actor, $object_actor); + $response = TypeActivity::post_activity ($follow_activity, $actor, $object_actor); $follow = Follow::create ([ "activity_id" => $follow_activity->id, - "actor" => $user->actor ()->first ()->id, + "actor" => $actor->id, "object" => $object_actor->id, ]); @@ -189,21 +187,21 @@ class APOutboxController extends Controller ]; } - public function handle_unfollow (User $user, string $object) + public function handle_unfollow (Actor $actor, string $object) { $object_actor = Actor::where ("actor_id", $object)->first (); if (!$object_actor) return response ()->json ([ "error" => "object not found" ], 404); - $follow_activity = Activity::where ("actor", $user->actor ()->first ()->actor_id) + $follow_activity = Activity::where ("actor", $actor->actor_id) ->where ("object", json_encode ($object_actor->actor_id, JSON_UNESCAPED_SLASHES)) ->where ("type", "Follow") ->first (); if (!$follow_activity) - return response ()->json ([ "error" => "no follow activity found. " . $user->actor ()->first ()->actor_id . " unfollowing " . $object_actor->actor_id ], 404); + return response ()->json ([ "error" => "no follow activity found. " . $actor->actor_id . " unfollowing " . $object_actor->actor_id ], 404); - $unfollow_activity = TypeActivity::craft_undo ($follow_activity, $user->actor ()->first ()); - $response = TypeActivity::post_activity ($unfollow_activity, $user->actor ()->first (), $object_actor); + $unfollow_activity = TypeActivity::craft_undo ($follow_activity, $actor); + $response = TypeActivity::post_activity ($unfollow_activity, $actor, $object_actor); // TODO: Check if it was successfully sent /* if (!$response || $response->getStatusCode () < 200 || $response->getStatusCode () >= 300) @@ -216,13 +214,12 @@ class APOutboxController extends Controller ]; } - public function handle_like (User $user, $request) + public function handle_like (Actor $actor, $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) { @@ -262,13 +259,12 @@ class APOutboxController extends Controller ]; } - public function handle_boost (User $user, $object) + public function handle_boost (Actor $actor, $object) { $object = Note::where ("note_id", $object)->first (); if (!$object) return response ()->json ([ "error" => "object not found" ], 404); - $actor = $user->actor ()->first (); $already_boosted = $actor->boosted_note ($object); if ($already_boosted) { @@ -302,13 +298,12 @@ class APOutboxController extends Controller ]; } - public function handle_pin (User $user, $object) + public function handle_pin (Actor $actor, $object) { $object = Note::where ("note_id", $object)->first (); if (!$object) return response ()->json ([ "error" => "object not found" ], 404); - $actor = $user->actor ()->first (); $already_pinned = $object->is_pinned ($actor); if ($already_pinned) { @@ -342,9 +337,8 @@ class APOutboxController extends Controller ]; } - public function handle_post (User $user, $request) + public function handle_post (Actor $actor, $request) { - $actor = $user->actor ()->first (); $note = TypeNote::craft_from_outbox ($actor, $request); if (isset ($request ["attachments"])) @@ -417,6 +411,18 @@ class APOutboxController extends Controller } $note->visibility = $request ["visibility"]; + + // if the parent note is not public, responses shouldn't be either + if ($request ["inReplyTo"]) + { + $parent_note = TypeNote::note_exists($request ["inReplyTo"]); + if ($parent_note) + { + $note->to = $parent_note->to; + $note->cc = $parent_note->cc; + $note->visibility = $parent_note->visibility; + } + } $note->save (); $create_activity = TypeActivity::craft_create ($actor, $note); diff --git a/app/Http/Controllers/AP/APWebfingerController.php b/app/Http/Controllers/AP/APWebfingerController.php index a6a261c..38d885d 100644 --- a/app/Http/Controllers/AP/APWebfingerController.php +++ b/app/Http/Controllers/AP/APWebfingerController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\AP; use App\Models\User; +use App\Models\Blog; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; @@ -32,7 +33,9 @@ class APWebfingerController extends Controller $user = $user[0]; $actual_user = User::where ("name", $user)->first (); if (!isset ($actual_user)) { - return response ()->json ([ "error" => "user not found" ], 404); + $actual_user = Blog::where ("slug", $user)->first (); + if (!$actual_user) + return response ()->json ([ "error" => "user not found" ], 404); } $webfinger = [ diff --git a/app/Http/Controllers/BlogController.php b/app/Http/Controllers/BlogController.php new file mode 100644 index 0000000..e438da0 --- /dev/null +++ b/app/Http/Controllers/BlogController.php @@ -0,0 +1,94 @@ +check ()) + $user = auth ()->user (); + + $blogs = Blog::orderBy ("created_at", "desc")->paginate (10); + + return view ("blogs", compact ("user", "blogs", "categories")); + } + + public function create () + { + $categories = BlogCategory::all (); + + return view ("blogs.create", compact ("categories")); + } + + public function store (Request $request) + { + if (!auth ()->check ()) + return redirect ()->route ("login")->with ("error", "You must be logged in to create a blog."); + + $request->validate ([ + "name" => "required|unique:users|unique:blogs", + "description" => "required", + "icon" => "required|image|max:4096", + "category" => "required" + ]); + + $user = auth ()->user (); + + $category = BlogCategory::find ($request->category); + if (!$category) + return redirect ()->route ("blogs.create")->with ("error", "Invalid category selected."); + + $icon = null; + $fname = $user->id . "-" . uniqid(); + if ($request->icon) + { + $manager = new ImageManager (new Driver ()); + $image = $manager->read ($request->file ("icon")); + $image_data = $image->cover (256, 256)->toJpeg (); + Storage::disk ("public")->put ("blog_icons/" . $fname . ".jpg", $image_data); + } + + $blog = Blog::create ([ + "name" => $request ["name"], + "slug" => Str::slug ($request ["name"]), + "description" => Str::markdown($request ["description"]), + "icon" => $fname . ".jpg", + "user_id" => $user->id, + "blog_category_id" => $category->id + ]); + + BlogCreatedEvent::dispatch ($blog, $user); + + return redirect ()->route ("blogs.show", [ 'blog' => $blog->slug ])->with ("success", "Blog created successfully!"); + } + + public function show (Blog $blog) + { + $notes = PaginationHelper::paginate ($blog->notes ()->orderBy ("created_at", "desc")->get (), 10); + + return view ("blogs.show", compact ("blog", "notes")); + } + + public function new_entry (Blog $blog) + { + return view ("blogs.new_entry", compact ("blog")); + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index fb9988b..5b32aeb 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -9,6 +9,7 @@ use App\Models\User; use App\Models\Actor; use App\Models\Note; use App\Models\Hashtag; +use App\Models\BlogCategory; use App\Helpers\PaginationHelper; diff --git a/app/Http/Controllers/PostController.php b/app/Http/Controllers/PostController.php index fe2a013..425e667 100644 --- a/app/Http/Controllers/PostController.php +++ b/app/Http/Controllers/PostController.php @@ -106,8 +106,7 @@ class PostController extends Controller if (!auth ()->check ()) return back ()->with ("error", "You need to be logged in to pin a post."); - $user = auth ()->user (); - $actor = $user->actor ()->first (); + $actor = $note->get_actor ()->first (); $response = ActionsPost::pin_post ($actor, $note); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index da5f4b6..224924a 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -11,6 +11,7 @@ use Intervention\Image\Drivers\Gd\Driver; use App\Models\User; use App\Models\Actor; use App\Models\Note; +use App\Models\Blog; use App\Actions\ActionsUser; use App\Helpers\PaginationHelper; @@ -168,8 +169,9 @@ class ProfileController extends Controller $ids = $user->mutual_friends (); if (request ()->get ("query")) { + $query = request ()->get ("query"); $friends = Actor::whereIn ("actor_id", $ids) - ->where ("preferredUsername", "like", "%" . request ()->get ("query") . "%") + ->where ("preferredUsername", "like", "%" . $query . "%") ->get (); } else @@ -231,4 +233,20 @@ class ProfileController extends Controller return view ("users.notifications", compact ("user", "notifications", "processed_notifications", "unread_notifications")); } + + public function blogs ($user_name) + { + if (str_starts_with ($user_name, "@")) + { + return redirect ()->route ("users.show", [ "user_name" => $user_name ]); + } + + $user = User::where ("name", $user_name)->first (); + if (!$user) + return redirect ()->route ("home"); + + $blogs = Blog::where ("user_id", $user->id)->orderBy ("created_at", "desc")->get (); + + return view ("users.blogs", compact ("user", "blogs")); + } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 225f6de..a9c234d 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -26,7 +26,7 @@ class UserController extends Controller public function do_signup (Request $request) { $incoming_fields = $request->validate ([ - "name" => "required|alpha_dash", + "name" => "required|alpha_dash|unique:users|unique:blogs", "email" => "required|email|unique:users", "password" => "required|confirmed" ]); diff --git a/app/Listeners/BlogCreatedListener.php b/app/Listeners/BlogCreatedListener.php new file mode 100644 index 0000000..9faaa73 --- /dev/null +++ b/app/Listeners/BlogCreatedListener.php @@ -0,0 +1,36 @@ +create_from_blog ($event->blog); + $actor->blog_id = $event->blog->id; + $actor->user_id = $event->user->id; + $actor->save (); + + $event->blog->actor_id = $actor->id; + $event->blog->save (); + } +} diff --git a/app/Models/Actor.php b/app/Models/Actor.php index 37fcd31..391fa15 100644 --- a/app/Models/Actor.php +++ b/app/Models/Actor.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Models\User; use App\Models\Announcement; use App\Models\Note; +use App\Models\Blog; use App\Helpers\PaginationHelper; @@ -19,6 +20,7 @@ class Actor extends Model "type", "actor_id", + "blog_id", "local_actor_id", "following", @@ -57,6 +59,11 @@ class Actor extends Model return $this->belongsTo (User::class); } + public function blog () + { + return $this->belongsTo (Blog::class); + } + public function profile_attachment () { return $this->hasMany (ProfileAttachment::class); @@ -84,6 +91,12 @@ class Actor extends Model return $this->create ($data); } + public function create_from_blog (Blog $blog) + { + $data = TypeActor::create_from_blog ($blog); + return $this->create ($data); + } + public static function build_response (Actor $actor) { return TypeActor::build_response ($actor); diff --git a/app/Models/Blog.php b/app/Models/Blog.php new file mode 100644 index 0000000..b57c64a --- /dev/null +++ b/app/Models/Blog.php @@ -0,0 +1,38 @@ +belongsTo (User::class); + } + + public function actor () + { + return $this->belongsTo (Actor::class); + } + + public function notes () + { + return $this->hasMany (Note::class, "actor_id", "actor_id"); + } + + public function pinned_notes () + { + return $this->hasMany (ProfilePin::class, "actor_id", "actor_id"); + } +} diff --git a/app/Models/BlogCategory.php b/app/Models/BlogCategory.php new file mode 100644 index 0000000..edf5640 --- /dev/null +++ b/app/Models/BlogCategory.php @@ -0,0 +1,13 @@ +belongsTo (Activity::class); + } + + public function note () + { + return $this->belongsTo (Note::class); + } + + public function actor () + { + return $this->belongsTo (Actor::class); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 7b9947d..0c1e85a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -99,6 +99,11 @@ class User extends Authenticatable return Cache::has ("user-online-" . $this->id); } + public function blogs () + { + return $this->hasMany (Blog::class); + } + public function mutual_friends () { $followers = Follow::where ("actor", $this->actor->id)->pluck ("object")->toArray (); diff --git a/app/Types/TypeActor.php b/app/Types/TypeActor.php index 999e562..fd95332 100644 --- a/app/Types/TypeActor.php +++ b/app/Types/TypeActor.php @@ -3,6 +3,7 @@ namespace App\Types; use App\Models\User; +use App\Models\Blog; use App\Models\Actor; use App\Models\ProfileAttachment; use App\Models\Instance; @@ -62,6 +63,40 @@ class TypeActor { ]; } + public static function create_from_blog (Blog $blog) + { + $keys = TypeActor::gen_keys (); + $app_url = env ("APP_URL"); + + return [ + "blog_id" => $blog->id, + + "type" => "Person", + "actor_id" => $app_url . "/ap/v1/user/" . $blog->slug, + + "inbox" => $app_url . "/ap/v1/user/" . $blog->slug . "/inbox", + "outbox" => $app_url . "/ap/v1/user/" . $blog->slug . "/outbox", + + "following" => $app_url . "/ap/v1/user/" . $blog->slug . "/following", + "followers" => $app_url . "/ap/v1/user/" . $blog->slug . "/followers", + + "liked" => $app_url . "/ap/v1/user/" . $blog->slug . "/liked", + "featured" => $app_url . "/ap/v1/user/" . $blog->slug . "/collections/featured", + "featured_tags" => $app_url . "/ap/v1/user/" . $blog->slug . "/collections/featured/tags", + + "sharedInbox" => $app_url . "/ap/v1/inbox", + + "preferredUsername" => $blog->slug, + "name" => $blog->name, + "summary" => $blog->description, + + "icon" => $app_url . "/storage/blog_icons/" . $blog->icon, + + "public_key" => $keys["public_key"]["key"], + "private_key" => $keys["private_key"] + ]; + } + public static function build_response (Actor $actor) { $response = [ @@ -122,7 +157,7 @@ class TypeActor { ] ]; - if ($actor->user) + if ($actor->user && !$actor->blog_id) { // appent to @context $response ["@context"][] = [ diff --git a/database/migrations/2025_01_12_225005_create_blogs_table.php b/database/migrations/2025_01_12_225005_create_blogs_table.php new file mode 100644 index 0000000..32055fe --- /dev/null +++ b/database/migrations/2025_01_12_225005_create_blogs_table.php @@ -0,0 +1,39 @@ +id(); + + $table->string ("name")->unique (); + $table->string ("slug")->unique (); + + $table->text ("description")->nullable (); + + $table->string ("icon")->nullable (); + + $table->foreignId ("user_id")->nullable ()->constrained ()->onDelete ("cascade"); + $table->foreignId ("actor_id")->nullable ()->constrained ()->onDelete ("cascade"); + $table->foreignId ("blog_category_id")->nullable ()->constrained (); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('blogs'); + } +}; diff --git a/database/migrations/2025_01_12_225655_create_blog_categories_table.php b/database/migrations/2025_01_12_225655_create_blog_categories_table.php new file mode 100644 index 0000000..b7d9e7f --- /dev/null +++ b/database/migrations/2025_01_12_225655_create_blog_categories_table.php @@ -0,0 +1,31 @@ +id(); + + $table->string ("name")->unique (); + $table->string ("slug")->unique (); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('blog_categories'); + } +}; diff --git a/database/migrations/2025_01_12_232530_add_fields_to_actors_table.php b/database/migrations/2025_01_12_232530_add_fields_to_actors_table.php new file mode 100644 index 0000000..6758828 --- /dev/null +++ b/database/migrations/2025_01_12_232530_add_fields_to_actors_table.php @@ -0,0 +1,29 @@ +foreignId ("blog_id")->nullable ()->after ("user_id")->constrained ()->onDelete ("cascade"); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('actors', function (Blueprint $table) { + $table->dropForeign (["blog_id"]); + $table->dropColumn ("blog_id"); + }); + } +}; diff --git a/database/seeders/BlogCategorySeeder.php b/database/seeders/BlogCategorySeeder.php new file mode 100644 index 0000000..19ab2aa --- /dev/null +++ b/database/seeders/BlogCategorySeeder.php @@ -0,0 +1,95 @@ + "Art", + "slug" => "art" + ], + [ + "name" => "Automotive", + "slug" => "automotive" + ], + [ + "name" => "Fashion", + "slug" => "fashion" + ], + [ + "name" => "Financial", + "slug" => "financial" + ], + [ + "name" => "Food", + "slug" => "food" + ], + [ + "name" => "Games", + "slug" => "games" + ], + [ + "name" => "Life", + "slug" => "life" + ], + [ + "name" => "Literature", + "slug" => "literature" + ], + [ + "name" => "Math & Science", + "slug" => "math-science" + ], + [ + "name" => "Movies & TV", + "slug" => "movies-tv" + ], + [ + "name" => "Music", + "slug" => "music" + ], + [ + "name" => "Paranormal", + "slug" => "paranormal" + ], + [ + "name" => "Politics", + "slug" => "politics" + ], + [ + "name" => "Humanity", + "slug" => "humanity" + ], + [ + "name" => "Romance", + "slug" => "romance" + ], + [ + "name" => "Sports", + "slug" => "sports" + ], + [ + "name" => "Technology", + "slug" => "technology" + ], + [ + "name" => "Travel", + "slug" => "travel" + ] + ]; + + foreach ($categories as $category) { + DB::table("blog_categories")->insert($category); + } + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d01a0ef..43eed67 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -15,9 +15,8 @@ class DatabaseSeeder extends Seeder { // User::factory(10)->create(); - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', + $this->call ([ + BlogCategorySeeder::class ]); } } diff --git a/resources/views/blogs.blade.php b/resources/views/blogs.blade.php new file mode 100644 index 0000000..4fe7d88 --- /dev/null +++ b/resources/views/blogs.blade.php @@ -0,0 +1,85 @@ +@extends ("partials.layout") + +@section ("title", "Blogs") + +@section ("content") +
+ + — by {{ $blog->user->name }} + — {{ count ($blog->notes) }} Posts +
+ + +You can use markdown in the description of your blog!
+You can use Markdown in the content of your entry!
+
+ ONLINE!
+
+ + Federation Handle: + +
+@php echo "@" . $blog->slug . "@" . explode ("/", env ("APP_URL"))[2] @endphp
++ +
+ +
- @if ($actor->user)
-
- @else
-
- @endif
+
+
+ + — by {{ $blog->user->name }} + — {{ count ($blog->notes) }} Posts +
+ + +Followed you
@elseif ($notification ['type'] == 'Unfollow') -Unfollowed you
+ @if ($notification ["object"]->id == auth ()->user ()->id) +You unfollowed you
+ @else +Unfollowed {{ $notification ['object']->name }}
+ @endif @elseif ($notification ['type'] == 'Boost')Boosted this post
@elseif ($notification ['type'] == 'Like') diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index 84e7ff6..6338e4b 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -50,7 +50,7 @@Mood: {{ $user->mood }}
- + @endif