Compare commits
10 Commits
9c3380a5ee
...
ded4ca5a1b
Author | SHA1 | Date | |
---|---|---|---|
ded4ca5a1b | |||
81f0d8818f | |||
25f00b1dc7 | |||
518586b6ab | |||
daa8b3eaeb | |||
d2a2e466ef | |||
7e3b868a90 | |||
c0ad0c46d6 | |||
dca3116d2a | |||
e31e7ff847 |
47
README.md
47
README.md
@ -164,7 +164,7 @@ server {
|
||||
server_name ws.ourspace.lat;
|
||||
root /var/www/html/ourspace/public;
|
||||
|
||||
location / {
|
||||
location /app {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Scheme $scheme;
|
||||
@ -173,16 +173,11 @@ server {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
|
||||
proxy_pass http://0.0.0.0:8080;
|
||||
}
|
||||
|
||||
location ~ ^/apps/(?<reverbid>[^/]+)/events$ { # variable reverbid
|
||||
proxy_pass http://0.0.0.0:8080/apps/$reverbid/events;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -200,20 +195,36 @@ 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 two services to handle the jobs that OurSpace needs to run and another one to run Laravel Reverb. So run something `emacs /lib/systemd/system/ourspace-queue.service` and `emacs /lib/systemd/system/ourspace-ws.service` and add the following content:
|
||||
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:
|
||||
|
||||
```ini
|
||||
# /lib/systemd/system/ourspace-queue.service
|
||||
[Unit]
|
||||
Description=OurSpace queue worker
|
||||
|
||||
[Service]
|
||||
User=www-data
|
||||
Group=www-data
|
||||
Restart=on-failure
|
||||
ExecStart=/usr/bin/php /var/www/html/ourspace/artisan queue:work --daemon --env=production --queue=ap
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```ini
|
||||
# /lib/systemd/system/ourspace-notifications.service
|
||||
[Unit]
|
||||
Description=OurSpace notifications worker
|
||||
|
||||
[Service]
|
||||
User=www-data
|
||||
Group=www-data
|
||||
@ -225,6 +236,7 @@ WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```ini
|
||||
# /lib/systemd/system/ourspace-ws.service
|
||||
[Unit]
|
||||
Description=OurSpace WebSockets Service
|
||||
|
||||
@ -238,8 +250,21 @@ ExecStart=/usr/bin/php /var/www/html/ourspace/artisan reverb:start
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Now reload the systemd daemon:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
Finally, enable and start both services, and your OurSpace instance will be ready to be used!
|
||||
|
||||
Aditionally, if you want to start multiple instances of the queue workers (which is ideal) you can name the service like `/lib/systemd/system/ourspace-queue@.service` and you can enable and manage them like this:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable ourspace-queue\{1..6} # enables 6 services
|
||||
sudo systemctl start ourspace-queue\{1..6} # starts 6 services
|
||||
```
|
||||
|
||||
## TODO:
|
||||
|
||||
For a list of planned features and improvements, please refer to the [TODO](TODO.md) file.
|
||||
|
6
TODO.md
6
TODO.md
@ -2,7 +2,7 @@
|
||||
|
||||
- [-] Activitypub
|
||||
- [x] Accounts
|
||||
- [-] Posts
|
||||
- [x] Posts
|
||||
- [x] Local posts should be federated
|
||||
- [x] Local posts should be deleted
|
||||
- [x] Remote posts should be fetched
|
||||
@ -18,10 +18,10 @@
|
||||
- [x] Tags
|
||||
- [x] Mentions
|
||||
- [x] Local mentions
|
||||
- [ ] Private post
|
||||
- [x] Private post
|
||||
- [x] Pinned Posts
|
||||
- [x] Nodeinfo
|
||||
- [ ] Notifications
|
||||
- [x] Notifications
|
||||
|
||||
- [-] Social features
|
||||
- [x] Profile
|
||||
|
@ -115,6 +115,10 @@ class ActionsPost
|
||||
|
||||
$processed = ActionsPost::process_content_and_attachments ($request);
|
||||
|
||||
$actor = null;
|
||||
if ($request ["blog_id"])
|
||||
$actor = Actor::where ("blog_id", $request ["blog_id"])->first ();
|
||||
else
|
||||
$actor = auth ()->user ()->actor ()->first ();
|
||||
|
||||
try {
|
||||
@ -125,6 +129,7 @@ class ActionsPost
|
||||
"summary" => $processed ["summary"],
|
||||
"content" => $processed ["content"],
|
||||
"attachments" => $processed ["attachments"],
|
||||
"visibility" => $request ["visibility"],
|
||||
"inReplyTo" => $processed ["inReplyTo"] ?? null,
|
||||
"tags" => $processed ["tags"] ?? null,
|
||||
"mentions" => $processed ["mentions"] ?? null
|
||||
|
26
app/Events/AP/ActivityFollowEvent.php
Normal file
26
app/Events/AP/ActivityFollowEvent.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\AP;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ActivityFollowEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct($activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
}
|
||||
}
|
26
app/Events/AP/ActivityLikeEvent.php
Normal file
26
app/Events/AP/ActivityLikeEvent.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\AP;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ActivityLikeEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct($activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
}
|
||||
}
|
@ -23,8 +23,6 @@ class ActivityUndoEvent
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $activity;
|
||||
public $actor;
|
||||
public $object;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
@ -32,31 +30,5 @@ class ActivityUndoEvent
|
||||
public function __construct($activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
|
||||
$this->actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]);
|
||||
|
||||
$child_activity = $activity ["object"];
|
||||
$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 ["error" => "Activity not found",];
|
||||
|
||||
$child_activity = Activity::where ("activity_id", $child_activity_id)->first ();
|
||||
$this->object = $child_activity;
|
||||
|
||||
switch ($this->object->type)
|
||||
{
|
||||
case "Follow":
|
||||
$unfollowed_actor = Actor::where ("actor_id", $this->object->object)->first ();
|
||||
UserUnfollowedEvent::dispatch ($this->object, $this->actor, $unfollowed_actor);
|
||||
break;
|
||||
}
|
||||
|
||||
$child_activity->delete ();
|
||||
}
|
||||
}
|
||||
|
31
app/Events/BlogCreatedEvent.php
Normal file
31
app/Events/BlogCreatedEvent.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Blog;
|
||||
use App\Models\User;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class BlogCreatedEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public Blog $blog;
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(Blog $blog, User $user)
|
||||
{
|
||||
$this->blog = $blog;
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@ -18,14 +18,14 @@ class NoteLikedEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public Activity $activity;
|
||||
public Actor $actor;
|
||||
public Note $note;
|
||||
public $activity;
|
||||
public $actor;
|
||||
public $note;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(Activity $activity, Actor $actor, Note $note)
|
||||
public function __construct($activity, $actor, $note)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
$this->actor = $actor;
|
||||
|
@ -19,13 +19,13 @@ class NoteRepliedEvent
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $activity;
|
||||
public Actor $actor;
|
||||
public Note $object;
|
||||
public $actor;
|
||||
public $object;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct($activity, Actor $actor, Note $object)
|
||||
public function __construct($activity, $actor, $object)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
$this->actor = $actor;
|
||||
|
@ -18,9 +18,9 @@ class UserFollowedEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public Activity $activity;
|
||||
public Actor $actor;
|
||||
public Actor $object;
|
||||
public $activity;
|
||||
public $actor;
|
||||
public $object;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
|
28
app/Events/UserSignedUp.php
Normal file
28
app/Events/UserSignedUp.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserSignedUp
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
@ -17,14 +17,14 @@ class UserUnfollowedEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public Activity $activity;
|
||||
public Actor $actor;
|
||||
public Actor $object;
|
||||
public $activity;
|
||||
public $actor;
|
||||
public $object;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(Activity $activity, Actor $actor, Actor $object)
|
||||
public function __construct($activity, $actor, $object)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
$this->actor = $actor;
|
||||
|
@ -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]));
|
||||
}
|
||||
|
||||
$actor = $user->actor ()->get ();
|
||||
$response = Actor::build_response ($actor->first ());
|
||||
return redirect (route ("users.show", ["user_name" => $actor->preferredUsername]));
|
||||
}
|
||||
|
||||
$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")) {
|
||||
|
@ -13,10 +13,11 @@ use App\Models\Like;
|
||||
use App\Types\TypeActor;
|
||||
use App\Types\TypeActivity;
|
||||
|
||||
use App\Events\UserFollowedEvent;
|
||||
use App\Events\NoteLikedEvent;
|
||||
|
||||
use App\Events\AP\ActivityUndoEvent;
|
||||
use App\Events\AP\ActivityFollowEvent;
|
||||
use App\Events\AP\ActivityLikeEvent;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@ -24,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");
|
||||
|
||||
@ -34,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:
|
||||
@ -52,71 +57,18 @@ class APInboxController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function handle_follow (User $user, $activity)
|
||||
private function handle_follow (Actor $actor, $activity)
|
||||
{
|
||||
if (TypeActivity::activity_exists ($activity["id"]))
|
||||
return response ()->json (["error" => "Activity already exists",], 409);
|
||||
ActivityFollowEvent::dispatch ($activity);
|
||||
}
|
||||
|
||||
$actor = TypeActor::actor_exists_or_obtain ($activity ["actor"]);
|
||||
|
||||
$target = TypeActor::actor_get_local ($activity ["object"]);
|
||||
if (!$target || !$target->user)
|
||||
return response ()->json (["error" => "Target not found",], 404);
|
||||
|
||||
// check follow doesn't exist
|
||||
$follow_exists = Follow::where ("actor", $actor->id)
|
||||
->where ("object", $target->id)
|
||||
->first ();
|
||||
if ($follow_exists)
|
||||
return response ()->json (["error" => "Follow already exists",], 409);
|
||||
|
||||
$activity ["activity_id"] = $activity ["id"];
|
||||
$act = Activity::create ($activity);
|
||||
|
||||
UserFollowedEvent::dispatch ($act, $actor, $target);
|
||||
|
||||
// TODO: Users should be able to manually check this
|
||||
$accept_activity = TypeActivity::craft_accept ($act);
|
||||
$response = TypeActivity::post_activity ($accept_activity, $target, $actor);
|
||||
if (!$response)
|
||||
public function handle_undo (Actor $actor, $activity)
|
||||
{
|
||||
return response ()->json ([
|
||||
"error" => "Error posting activity",
|
||||
], 500);
|
||||
}
|
||||
ActivityUndoEvent::dispatch ($activity);
|
||||
}
|
||||
|
||||
public function handle_undo (User $user, $activity)
|
||||
public function handle_like (Actor $actor, $activity)
|
||||
{
|
||||
ActivityUndoEvent::dispatch ($activity, $activity);
|
||||
return response ()->json (ActionsActivity::activity_undo ($activity));
|
||||
}
|
||||
|
||||
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 ();
|
||||
|
||||
NoteLikedEvent::dispatch ($act, $actor, $note);
|
||||
|
||||
return response ()->json (["success" => "Like created",], 200);
|
||||
ActivityLikeEvent::dispatch ($activity);
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ class APInstanceInboxController extends Controller
|
||||
$note_actor = $note_exists->get_actor ()->first ();
|
||||
if ($note_actor->user)
|
||||
{
|
||||
$note_actor->user->notify (new UserNotification("Boost", $announcement_actor, $note_exists));
|
||||
$note_actor->user->notify (new UserNotification("Boost", $announcement_actor->id, $note_exists->id));
|
||||
}
|
||||
|
||||
return response ()->json (["status" => "ok"]);
|
||||
|
@ -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"]))
|
||||
@ -370,6 +364,7 @@ class APOutboxController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
$mentions = [];
|
||||
if (isset ($request ["mentions"]))
|
||||
{
|
||||
foreach ($request ["mentions"] as $mention)
|
||||
@ -387,10 +382,54 @@ class APOutboxController extends Controller
|
||||
"note_id" => $note->id,
|
||||
"actor_id" => $object->id
|
||||
]);
|
||||
|
||||
$mentions[] = $object->actor_id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($request ["visibility"] == "public")
|
||||
{
|
||||
$note->to = [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
];
|
||||
$note->cc = [
|
||||
$actor->followers
|
||||
];
|
||||
}
|
||||
else if ($request ["visibility"] == "followers")
|
||||
{
|
||||
// TODO: Boosting should be disabled
|
||||
$note->to = [
|
||||
$actor->followers
|
||||
];
|
||||
$note->cc = [];
|
||||
}
|
||||
else if ($request ["visibility"] == "private")
|
||||
{
|
||||
// TODO: Boosting should be disabled
|
||||
$note->to = $mentions;
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
$create_activity->to = $note->to;
|
||||
$create_activity->cc = $note->cc;
|
||||
|
||||
$note->activity_id = $create_activity->id;
|
||||
$note->save ();
|
||||
|
||||
|
@ -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,6 +33,8 @@ class APWebfingerController extends Controller
|
||||
$user = $user[0];
|
||||
$actual_user = User::where ("name", $user)->first ();
|
||||
if (!isset ($actual_user)) {
|
||||
$actual_user = Blog::where ("slug", $user)->first ();
|
||||
if (!$actual_user)
|
||||
return response ()->json ([ "error" => "user not found" ], 404);
|
||||
}
|
||||
|
||||
|
94
app/Http/Controllers/BlogController.php
Normal file
94
app/Http/Controllers/BlogController.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Blog;
|
||||
use App\Events\BlogCreatedEvent;
|
||||
|
||||
use App\Models\BlogCategory;
|
||||
|
||||
use App\Helpers\PaginationHelper;
|
||||
|
||||
use Intervention\Image\ImageManager;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BlogController extends Controller
|
||||
{
|
||||
public function index ()
|
||||
{
|
||||
$categories = BlogCategory::all ();
|
||||
$user = null;
|
||||
|
||||
if (auth ()->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"));
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
@ -42,21 +43,30 @@ class HomeController extends Controller
|
||||
|
||||
if (request ()->get ("posts") == "latest")
|
||||
{
|
||||
$notes = Note::latest ()->where ("in_reply_to", null)->take (10)->get ();
|
||||
$notes = Note::latest ();
|
||||
}
|
||||
else
|
||||
{
|
||||
$notes = Note::withCount ([ "get_likes" => function ($query) {
|
||||
$query->where ("created_at", ">=", now ()->subDay ());
|
||||
}])->where ("in_reply_to", null)->orderBy ("get_likes_count", "desc")->take (8)->get ();
|
||||
}])->where ("in_reply_to", null)->orderBy ("get_likes_count", "desc");
|
||||
}
|
||||
|
||||
$notes = $notes->paginate (10);
|
||||
|
||||
return view ("browse", compact ("users", "popular_hashtags", "notes"));
|
||||
}
|
||||
|
||||
public function tag ($tag)
|
||||
{
|
||||
dd ($tag);
|
||||
$tag_name = "#" . $tag;
|
||||
$hashtag = Hashtag::where ("name", $tag_name)->first ();
|
||||
if (!$hashtag)
|
||||
return redirect ()->route ("browse");
|
||||
|
||||
$posts = $hashtag->get_notes ()->paginate (20);
|
||||
|
||||
return view ("posts.tag", compact ("hashtag", "posts"));
|
||||
}
|
||||
|
||||
public function search ()
|
||||
|
@ -18,6 +18,11 @@ class PostController extends Controller
|
||||
{
|
||||
$actor = $note->get_actor ()->first ();
|
||||
|
||||
if (!$actor)
|
||||
return redirect ()->back ();
|
||||
|
||||
if (!$note->can_view ())
|
||||
return redirect ()->back ()->with ("error", "You are not allowed to view this post.");
|
||||
return view ("posts.show", compact ("note", "actor"));
|
||||
}
|
||||
|
||||
@ -101,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);
|
||||
|
||||
|
@ -10,8 +10,11 @@ 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;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
@ -55,7 +58,8 @@ class ProfileController extends Controller
|
||||
|
||||
$incoming_fields = $request->validate ([
|
||||
"avatar" => "image|max:4096",
|
||||
"song" => "file|mimes:audio/mpeg,mp3|max:1024",
|
||||
"song" => "file|mimes:audio/mpeg,mp3|max:4096",
|
||||
"notification_sound" => "file|mimes:audio/mpeg,mp3|max:1024",
|
||||
"bio" => "sometimes|nullable|string",
|
||||
"about_you" => "sometimes|nullable|string",
|
||||
"status" => "sometimes|nullable|string",
|
||||
@ -74,9 +78,11 @@ class ProfileController extends Controller
|
||||
|
||||
$changing_avatar = false;
|
||||
$changing_song = false;
|
||||
$changing_notification_sound = false;
|
||||
|
||||
$old_avatar = null;
|
||||
$old_song = null;
|
||||
$old_notification_sound = null;
|
||||
if (isset ($incoming_fields["avatar"]) && !empty ($incoming_fields["avatar"]))
|
||||
{
|
||||
$manager = new ImageManager (new Driver ());
|
||||
@ -102,6 +108,16 @@ class ProfileController extends Controller
|
||||
$changing_song = true;
|
||||
}
|
||||
|
||||
if (isset ($incoming_fields ["notification_sound"]) && !empty ($incoming_fields["notification_sound"]))
|
||||
{
|
||||
$file = $request->file ("notification_sound");
|
||||
Storage::disk ("public")->put ("notification_sounds/" . $fname . ".mp3", file_get_contents ($file));
|
||||
|
||||
$old_notification_sound = "notification_sounds/" . $user->notification_sound;
|
||||
$user->notification_sound = $fname . ".mp3";
|
||||
$changing_notification_sound = true;
|
||||
}
|
||||
|
||||
$user->bio = $incoming_fields["bio"];
|
||||
$user->about_you = $incoming_fields["about_you"];
|
||||
$user->status = $incoming_fields["status"];
|
||||
@ -126,6 +142,9 @@ class ProfileController extends Controller
|
||||
if ($changing_song)
|
||||
Storage::disk ("public")->delete (str_replace ("/storage/", "", $old_song));
|
||||
|
||||
if ($changing_notification_sound)
|
||||
Storage::disk ("public")->delete (str_replace ("/storage/", "", $old_notification_sound));
|
||||
|
||||
$response = ActionsUser::update_profile ();
|
||||
if (isset ($response["error"]))
|
||||
return back ()->with ("error", "Error updating profile: " . $response["error"]);
|
||||
@ -150,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
|
||||
@ -161,4 +181,72 @@ class ProfileController extends Controller
|
||||
|
||||
return view ("users.friends", compact ("actor", "user", "friends"));
|
||||
}
|
||||
|
||||
public function notifications ()
|
||||
{
|
||||
if (!auth ()->check ())
|
||||
return redirect ()->route ("login");
|
||||
|
||||
$user = auth ()->user ();
|
||||
$user_notifications = $user->notifications;
|
||||
$notifications = PaginationHelper::paginate (collect ($user_notifications), 20);
|
||||
$unread_notifications = $user->unreadNotifications->count ();
|
||||
$processed_notifications = [];
|
||||
|
||||
foreach ($notifications as $notification)
|
||||
{
|
||||
$data = $notification->data;
|
||||
$type = $data ['type'];
|
||||
$actor = Actor::find ($data["actor"]);
|
||||
$object = null;
|
||||
|
||||
switch ($type)
|
||||
{
|
||||
case "Follow":
|
||||
case "Unfollow":
|
||||
$object = Actor::find ($data["object"]);
|
||||
break;
|
||||
|
||||
case "Signup":
|
||||
$object = User::find ($data ["object"]);
|
||||
break;
|
||||
|
||||
case "Like":
|
||||
case "Reply":
|
||||
case "Boost":
|
||||
case "Mention":
|
||||
$object = Note::find ($data["object"]);
|
||||
break;
|
||||
}
|
||||
|
||||
$processed_notifications[] = [
|
||||
"id"=> $notification->id,
|
||||
"type" => $type,
|
||||
"actor" => $actor,
|
||||
"object" => $object,
|
||||
"created_at" => $notification->created_at,
|
||||
"read_at" => $notification->read_at
|
||||
];
|
||||
|
||||
$notification->markAsRead ();
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ class UserActionController extends Controller
|
||||
$request->validate ([
|
||||
"summary" => "nullable|string",
|
||||
"content" => "required",
|
||||
"files.*" => "max:4096"
|
||||
"files.*" => "max:4096",
|
||||
"visibility" => "required|in:public,private,followers",
|
||||
]);
|
||||
|
||||
$response = ActionsPost::post_new ($request);
|
||||
|
@ -7,6 +7,8 @@ use App\Models\Actor;
|
||||
|
||||
use App\Actions\ActionsFriends;
|
||||
|
||||
use App\Events\UserSignedUp;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserController extends Controller
|
||||
@ -24,20 +26,15 @@ 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"
|
||||
]);
|
||||
|
||||
$user = User::create ($incoming_fields);
|
||||
$actor = new Actor ();
|
||||
$actor->create_from_user ($user);
|
||||
auth ()->login ($user);
|
||||
|
||||
// create a friendship between the new user and the admin
|
||||
$admin = User::where ("is_admin", 1)->first ();
|
||||
if ($admin)
|
||||
ActionsFriends::force_friendship ($user, $admin);
|
||||
UserSignedUp::dispatch ($user);
|
||||
auth ()->login ($user);
|
||||
|
||||
return redirect ()->route ("home")->with ("success", "You have successfuly signed up!");
|
||||
}
|
||||
|
58
app/Listeners/AP/ActivityFollowListener.php
Normal file
58
app/Listeners/AP/ActivityFollowListener.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\AP;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\Follow;
|
||||
|
||||
use App\Types\TypeActivity;
|
||||
use App\Types\TypeActor;
|
||||
|
||||
use App\Events\UserFollowedEvent;
|
||||
|
||||
use App\Events\AP\ActivityFollowEvent;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class ActivityFollowListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(ActivityFollowEvent $event): void
|
||||
{
|
||||
if (TypeActivity::activity_exists ($event->activity["id"]))
|
||||
return;
|
||||
|
||||
$actor = TypeActor::actor_exists_or_obtain ($event->activity ["actor"]);
|
||||
|
||||
$target = TypeActor::actor_get_local ($event->activity ["object"]);
|
||||
if (!$target || !$target->user)
|
||||
return;
|
||||
|
||||
// check follow doesn't exist
|
||||
$follow_exists = Follow::where ("actor", $actor->id)
|
||||
->where ("object", $target->id)
|
||||
->first ();
|
||||
if ($follow_exists)
|
||||
return;
|
||||
|
||||
$event->activity ["activity_id"] = $event->activity ["id"];
|
||||
$act = Activity::create ($event->activity);
|
||||
|
||||
UserFollowedEvent::dispatch ($act, $actor, $target);
|
||||
|
||||
// TODO: Users should be able to manually check this
|
||||
$accept_activity = TypeActivity::craft_accept ($act);
|
||||
TypeActivity::post_activity ($accept_activity, $target, $actor);
|
||||
}
|
||||
}
|
57
app/Listeners/AP/ActivityLikeListener.php
Normal file
57
app/Listeners/AP/ActivityLikeListener.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\AP;
|
||||
|
||||
use App\Models\Note;
|
||||
use App\Models\Activity;
|
||||
|
||||
use App\Types\TypeActivity;
|
||||
use App\Types\TypeActor;
|
||||
|
||||
use App\Events\AP\ActivityLikeEvent;
|
||||
|
||||
use App\Events\NoteLikedEvent;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class ActivityLikeListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(ActivityLikeEvent $event): void
|
||||
{
|
||||
$actor = TypeActor::actor_exists_or_obtain ($event->activity ["actor"]);
|
||||
$note_id = $event->activity ["object"];
|
||||
$note = Note::where ("note_id", $note_id)->first ();
|
||||
if (!$note)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check like doesn't already exist
|
||||
$like_exists = $actor->liked_note ($note);
|
||||
if ($like_exists)
|
||||
return;
|
||||
|
||||
$event->activity ["activity_id"] = $event->activity ["id"];
|
||||
$activity_exists = TypeActivity::activity_exists ($event->activity ["id"]);
|
||||
if (!$activity_exists)
|
||||
$act = Activity::create ($event->activity);
|
||||
else
|
||||
$act = Activity::where ("activity_id", $event->activity ["id"])->first ();
|
||||
|
||||
NoteLikedEvent::dispatch ($act, $actor, $note);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
58
app/Listeners/AP/ActivityUndoListener.php
Normal file
58
app/Listeners/AP/ActivityUndoListener.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners\AP;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\Actor;
|
||||
|
||||
use App\Types\TypeActor;
|
||||
use App\Types\TypeActivity;
|
||||
|
||||
use App\Events\AP\ActivityUndoEvent;
|
||||
use App\Events\UserUnfollowedEvent;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class ActivityUndoListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(ActivityUndoEvent $event): void
|
||||
{
|
||||
$actor = TypeActor::actor_exists_or_obtain ($event->activity ["actor"]);
|
||||
|
||||
$child_activity = $event->activity ["object"];
|
||||
$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;
|
||||
|
||||
$child_activity = Activity::where ("activity_id", $child_activity_id)->first ();
|
||||
$object = $child_activity;
|
||||
|
||||
switch ($object->type)
|
||||
{
|
||||
case "Follow":
|
||||
$unfollowed_actor = Actor::where ("actor_id", $object->object)->first ();
|
||||
UserUnfollowedEvent::dispatch ($object, $actor, $unfollowed_actor);
|
||||
break;
|
||||
}
|
||||
|
||||
$child_activity->delete ();
|
||||
}
|
||||
}
|
36
app/Listeners/BlogCreatedListener.php
Normal file
36
app/Listeners/BlogCreatedListener.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Models\Actor;
|
||||
use App\Types\TypeActor;
|
||||
|
||||
use App\Events\BlogCreatedEvent;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class BlogCreatedListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(BlogCreatedEvent $event): void
|
||||
{
|
||||
$actor = new Actor ();
|
||||
$actor = $actor->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 ();
|
||||
}
|
||||
}
|
@ -29,12 +29,9 @@ class NoteRepliedListener
|
||||
{
|
||||
$note_actor = $event->object->get_actor ()->first ();
|
||||
|
||||
Log::info ("hi");
|
||||
if (!$note_actor || !$note_actor->user)
|
||||
return;
|
||||
|
||||
Log::info ("bai");
|
||||
|
||||
$note_actor->user->notify (new UserNotification(
|
||||
"Reply",
|
||||
$event->actor->id,
|
||||
|
48
app/Listeners/UserSignedUpListener.php
Normal file
48
app/Listeners/UserSignedUpListener.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Actor;
|
||||
|
||||
use App\Actions\ActionsFriends;
|
||||
|
||||
use App\Events\UserSignedUp;
|
||||
|
||||
use App\Notifications\UserNotification;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class UserSignedUpListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(UserSignedUp $event): void
|
||||
{
|
||||
$actor = new Actor ();
|
||||
$actor->create_from_user ($event->user);
|
||||
|
||||
// create a friendship between the new user and the admin
|
||||
$admin = User::where ("is_admin", 1)->first ();
|
||||
if ($admin)
|
||||
{
|
||||
ActionsFriends::force_friendship ($event->user, $admin);
|
||||
|
||||
$admin->notify (new UserNotification(
|
||||
"Signup",
|
||||
$actor->id,
|
||||
$event->user->id
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
38
app/Models/Blog.php
Normal file
38
app/Models/Blog.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Blog extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
"name",
|
||||
"slug",
|
||||
"description",
|
||||
"icon",
|
||||
"user_id",
|
||||
"actor_id",
|
||||
"blog_category_id"
|
||||
];
|
||||
|
||||
public function user ()
|
||||
{
|
||||
return $this->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");
|
||||
}
|
||||
}
|
13
app/Models/BlogCategory.php
Normal file
13
app/Models/BlogCategory.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BlogCategory extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
"name",
|
||||
"slug"
|
||||
];
|
||||
}
|
@ -11,6 +11,6 @@ class Hashtag extends Model
|
||||
];
|
||||
|
||||
public function get_notes () {
|
||||
return $this->belongsToMany(Note::class, 'note_hashtag');
|
||||
return $this->belongsToMany(Note::class, 'note_hashtag')->orderBy('created_at', 'desc');
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ class Note extends Model
|
||||
"content",
|
||||
"tag",
|
||||
"to",
|
||||
"cc"
|
||||
"cc",
|
||||
"visibility"
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -88,4 +89,34 @@ class Note extends Model
|
||||
{
|
||||
return ProfilePin::where ("actor_id", $actor->id)->where ("note_id", $this->id)->first ();
|
||||
}
|
||||
|
||||
public function can_view (Actor $actor = null)
|
||||
{
|
||||
$final_actor = $actor;
|
||||
$note_actor = $this->get_actor ()->first ();
|
||||
if (!$final_actor && auth ()->check ())
|
||||
{
|
||||
$final_actor = auth ()->user ()->actor;
|
||||
}
|
||||
|
||||
if ($this->visibility == "public")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ($this->visibility == "followers" && $final_actor)
|
||||
{
|
||||
return $final_actor->friends_with ($note_actor);
|
||||
}
|
||||
else if ($this->visibility == "private" && $final_actor)
|
||||
{
|
||||
if ($final_actor == $note_actor)
|
||||
return true;
|
||||
|
||||
$mention_exists = NoteMention::where ("note_id", $this->id)->where ("actor_id", $final_actor->id)->first ();
|
||||
if ($mention_exists)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -11,4 +11,19 @@ class ProfilePin extends Model
|
||||
"note_id",
|
||||
"actor_id"
|
||||
];
|
||||
|
||||
public function activity ()
|
||||
{
|
||||
return $this->belongsTo (Activity::class);
|
||||
}
|
||||
|
||||
public function note ()
|
||||
{
|
||||
return $this->belongsTo (Note::class);
|
||||
}
|
||||
|
||||
public function actor ()
|
||||
{
|
||||
return $this->belongsTo (Actor::class);
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +82,13 @@ class User extends Authenticatable
|
||||
});
|
||||
}
|
||||
|
||||
protected function notificationSound () : Attribute
|
||||
{
|
||||
return Attribute::make (get: function ($value) {
|
||||
return $value ? "/storage/notification_sounds/" . $value : "/resources/sounds/notification.mp3";
|
||||
});
|
||||
}
|
||||
|
||||
public function actor ()
|
||||
{
|
||||
return $this->hasOne (Actor::class);
|
||||
@ -92,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 ();
|
||||
|
@ -4,10 +4,11 @@ namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\BroadcastMessage;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class UserNotification extends Notification
|
||||
class UserNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
|
@ -16,7 +16,9 @@ class TypeActivity {
|
||||
public static function craft_response (Activity $activity)
|
||||
{
|
||||
$crafted_activity = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
],
|
||||
"id" => $activity->activity_id,
|
||||
"type" => $activity->type,
|
||||
"actor" => $activity->actor,
|
||||
@ -237,7 +239,7 @@ class TypeActivity {
|
||||
|
||||
public static function post_activity (Activity $activity, Actor $source, $target, $should_sign = false)
|
||||
{
|
||||
PostActivityJob::dispatch ($activity, $source, $target, $should_sign);
|
||||
PostActivityJob::dispatch ($activity, $source, $target, $should_sign)->onQueue ("ap");
|
||||
|
||||
return [
|
||||
"success" => "activity posted"
|
||||
|
@ -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"][] = [
|
||||
@ -209,6 +244,8 @@ class TypeActor {
|
||||
// we need to save the model first
|
||||
$actor->save ();
|
||||
|
||||
if (isset ($request ["attachment"]))
|
||||
{
|
||||
ProfileAttachment::where ("actor_id", $actor->id)->delete ();
|
||||
foreach ($request ["attachment"] as $attachment)
|
||||
{
|
||||
@ -218,6 +255,7 @@ class TypeActor {
|
||||
"content" => $attachment ["value"]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$featured_items = TypeActor::actor_process_featured ($actor);
|
||||
ProfilePin::where ("actor_id", $actor->id)->delete ();
|
||||
|
@ -86,11 +86,6 @@ class TypeNote
|
||||
"attributedTo" => $actor->actor_id,
|
||||
"content" => $request ["content"] ?? null,
|
||||
"tag" => $request ["tag"] ?? null,
|
||||
|
||||
// TODO: This should change when I implement visibilities and private notes
|
||||
"cc" => [
|
||||
$actor->followers
|
||||
]
|
||||
]);
|
||||
|
||||
$note->url = route ('posts.show', $note->id);
|
||||
@ -104,6 +99,7 @@ class TypeNote
|
||||
if ($activity)
|
||||
$note->activity_id = $activity->id;
|
||||
|
||||
$note_actor = $actor;
|
||||
if ($actor)
|
||||
$note->actor_id = $actor->id;
|
||||
else
|
||||
@ -119,6 +115,7 @@ class TypeNote
|
||||
}
|
||||
|
||||
$note->actor_id = $actor->id;
|
||||
$note_actor = $actor;
|
||||
}
|
||||
|
||||
$note->note_id = $request["id"] ?? null;
|
||||
@ -129,6 +126,8 @@ class TypeNote
|
||||
$note->content = $request["content"] ?? null;
|
||||
$note->tag = $request["tag"] ?? null;
|
||||
$note->created_at = $request["published"] ?? null;
|
||||
$note->to = $request["to"] ?? null;
|
||||
$note->cc = $request["cc"] ?? null;
|
||||
|
||||
$attachments = $note->attachments ()->get ();
|
||||
foreach ($attachments as $attachment)
|
||||
@ -222,8 +221,8 @@ class TypeNote
|
||||
{
|
||||
$actor_exists->user->notify (new UserNotification(
|
||||
"Mention",
|
||||
$actor,
|
||||
$actor_exists
|
||||
$actor->id,
|
||||
$note->id
|
||||
));
|
||||
}
|
||||
break;
|
||||
@ -235,6 +234,20 @@ class TypeNote
|
||||
{
|
||||
// TODO: Handle replies
|
||||
}
|
||||
|
||||
$note_to = $note->to;
|
||||
if (in_array ("https://www.w3.org/ns/activitystreams#Public", $note_to))
|
||||
{
|
||||
$note->visibility = "public";
|
||||
}
|
||||
else if (in_array ($note_actor->followers, $note_to))
|
||||
{
|
||||
$note->visibility = "followers";
|
||||
}
|
||||
else
|
||||
{
|
||||
$note->visibility = "private";
|
||||
}
|
||||
}
|
||||
|
||||
public static function create_from_request ($request, Activity $activity, Actor $actor)
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?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::table('notes', function (Blueprint $table) {
|
||||
$table->string ("visibility")->default ("public");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('notes', function (Blueprint $table) {
|
||||
$table->dropColumn ("visibility");
|
||||
});
|
||||
}
|
||||
};
|
49
database/migrations/2025_01_12_225005_create_blogs_table.php
Normal file
49
database/migrations/2025_01_12_225005_create_blogs_table.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?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('blog_categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string ("name")->unique ();
|
||||
$table->string ("slug")->unique ();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('blogs', function (Blueprint $table) {
|
||||
$table->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');
|
||||
Schema::dropIfExists('blog_categories');
|
||||
}
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
<?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::table('actors', function (Blueprint $table) {
|
||||
$table->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");
|
||||
});
|
||||
}
|
||||
};
|
95
database/seeders/BlogCategorySeeder.php
Normal file
95
database/seeders/BlogCategorySeeder.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
|
||||
class BlogCategorySeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$categories = [
|
||||
[
|
||||
"name" => "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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
85
resources/views/blogs.blade.php
Normal file
85
resources/views/blogs.blade.php
Normal file
@ -0,0 +1,85 @@
|
||||
@extends ("partials.layout")
|
||||
|
||||
@section ("title", "Blogs")
|
||||
|
||||
@section ("content")
|
||||
<div class="row blog-category">
|
||||
<div class="col w-20 left">
|
||||
<div class="category-list">
|
||||
<b>View:</b>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ route ('blogs') }}">
|
||||
<img loading="lazy" src="/resources/icons/clock.png" class="icon">
|
||||
<b>Recent Blogs</b>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{{-- TODO: Top entries and blogs I'm following --}}
|
||||
</ul>
|
||||
|
||||
<b>Categories:</b>
|
||||
<ul>
|
||||
@foreach ($categories as $category)
|
||||
<li>
|
||||
<a href="#">{{ $category->name }}</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col right">
|
||||
<h1>Blogs</h1>
|
||||
|
||||
@auth
|
||||
<div class="blog-preview">
|
||||
<h3>
|
||||
[
|
||||
<a href="{{ route ('blogs.create') }}">Create a blog</a>
|
||||
]
|
||||
</h3>
|
||||
<h3>
|
||||
[
|
||||
<a href="#">View your blogs</a>
|
||||
]
|
||||
</h3>
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Latest Blogs</h3>
|
||||
<div class="blog-entries">
|
||||
@foreach ($blogs as $blog)
|
||||
<div class="entry">
|
||||
<p class="publish-date">
|
||||
<time class="ago">{{ $blog->created_at->diffForHumans () }}</time>
|
||||
— by <a href="{{ route ('users.show', [ 'user_name' => $blog->user->name ]) }}">{{ $blog->user->name }}</a>
|
||||
— <a>{{ count ($blog->notes) }} Posts</a>
|
||||
</p>
|
||||
|
||||
<div class="inner">
|
||||
<h3 class="title">
|
||||
<a href="{{ route ('blogs.show', [ 'blog' => $blog ]) }}">
|
||||
{{ $blog->name }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
{!! $blog->description !!}
|
||||
</p>
|
||||
|
||||
<a href="{{ route ('blogs.show', [ 'blog' => $blog ]) }}">
|
||||
» Read more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
{{ $blogs->links ("pagination::default") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
52
resources/views/blogs/create.blade.php
Normal file
52
resources/views/blogs/create.blade.php
Normal file
@ -0,0 +1,52 @@
|
||||
@extends ("partials.layout")
|
||||
|
||||
@section ("title", "Create a new blog")
|
||||
|
||||
@section ("content")
|
||||
<div class="row edit-blog-entry">
|
||||
<div class="col w-20 left">
|
||||
<div class="edit-info">
|
||||
<p>You can use markdown in the description of your blog!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col right">
|
||||
<h1>Create Blog</h1>
|
||||
<br>
|
||||
|
||||
<form method="POST" class="ctrl-enter-submit" enctype="multipart/form-data">
|
||||
@csrf
|
||||
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" id="name" value="{{ old ('name') }}" required>
|
||||
@error("name")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
<label for="description">Description:</label>
|
||||
<textarea name="description" id="description">{{ old ("description") }}</textarea>
|
||||
@error("description")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
<label for="icon">Logo:</label>
|
||||
<input type="file" name="icon" id="icon" accept="image/*" required>
|
||||
@error("icon")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
<br>
|
||||
<label for="category">Category:</label>
|
||||
<select name="category" id="category">
|
||||
@foreach ($categories as $category)
|
||||
<option value="{{ $category->id }}">{{ $category->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
<div class="publish">
|
||||
<button type="submit" name="submit">Create Blog!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
56
resources/views/blogs/new_entry.blade.php
Normal file
56
resources/views/blogs/new_entry.blade.php
Normal file
@ -0,0 +1,56 @@
|
||||
@extends ("partials.layout")
|
||||
|
||||
@section ("title", "Create New Entry")
|
||||
|
||||
@section ("content")
|
||||
<div class="row edit-blog-entry">
|
||||
<div class="col w-20 left">
|
||||
<div class="edit-info">
|
||||
<p>You can use Markdown in the content of your entry!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col right">
|
||||
<h1>New Entry</h1>
|
||||
<br>
|
||||
|
||||
<form method="POST" class="ctrl-enter-submit" enctype="multipart/form-data" action="{{ route ('user.post.new') }}">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="blog_id" value="{{ $blog->id }}">
|
||||
|
||||
<label for="summary">Title:</label>
|
||||
<input type="text" name="summary" id="summary" value="{{ old ('summary') }}" placeholder="A really cool post!" required>
|
||||
@error("summary")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
<br>
|
||||
|
||||
<textarea name="content" placeholder="What's on your mind?">{{ old ('content') }}</textarea>
|
||||
<small>Markdown is supported</small>
|
||||
@error("content")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
<br>
|
||||
|
||||
<label for="files">Attachments:</label><br>
|
||||
<input type="file" name="files[]" accept="image/*" id="files" multiple><br>
|
||||
@error("files.*")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
<br>
|
||||
|
||||
<label for="visibility">Visibility</label>
|
||||
<select name="visibility">
|
||||
<option value="public">Public</option>
|
||||
<option value="followers">Friends only</option>
|
||||
<option value="private">Private</option>
|
||||
</select>
|
||||
<br><br>
|
||||
|
||||
<button type="submit">Post!</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
88
resources/views/blogs/show.blade.php
Normal file
88
resources/views/blogs/show.blade.php
Normal file
@ -0,0 +1,88 @@
|
||||
@extends ("partials.layout")
|
||||
|
||||
@section ("title", $blog->name)
|
||||
|
||||
@section ("content")
|
||||
<div class="row profile">
|
||||
<div class="col w-30 left">
|
||||
<h1>
|
||||
{{ $blog->name }}
|
||||
</h1>
|
||||
|
||||
<div class="general-about">
|
||||
<div class="profile-pic">
|
||||
<img loading="lazy" src="{{ $blog->actor->icon }}" alt="{{ $blog->name }}" class="pfp-fallback">
|
||||
</div>
|
||||
|
||||
<div class="details below">
|
||||
@if ($blog->user->is_online ())
|
||||
<p class="online">
|
||||
<img loading="lazy" src="/resources/img/green_person.png"> ONLINE!
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mood">
|
||||
<p><b>Mood:</b> {{ $blog->user->mood }}</p>
|
||||
<br>
|
||||
<p>
|
||||
<b>View my: <a href="{{ route ('users.show', [ 'user_name' => $blog->user->name ]) }}">Profile</a></b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="url-info">
|
||||
<p>
|
||||
<b>
|
||||
Federation Handle:
|
||||
</b>
|
||||
</p>
|
||||
<p>@php echo "@" . $blog->slug . "@" . explode ("/", env ("APP_URL"))[2] @endphp</p>
|
||||
</div>
|
||||
|
||||
<div class="url-info view-full-profile">
|
||||
<p>
|
||||
<a href="{{ route ('users.show', [ 'user_name' => $blog->user->name ]) }}">
|
||||
<b>View Full Profile</b>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col right">
|
||||
<div class="blog-preview">
|
||||
<h1>
|
||||
{{ $blog->name }}'s Blog Entries
|
||||
</h1>
|
||||
|
||||
<div class="blog-preview">
|
||||
<h3>
|
||||
[
|
||||
<a href="{{ route ('blogs.new_entry', [ 'blog' => $blog->slug ]) }}">
|
||||
New Entry
|
||||
</a>
|
||||
]
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="blog-entries">
|
||||
<h3>Pinned</h3>
|
||||
|
||||
@foreach ($blog->pinned_notes as $note)
|
||||
<x-blog_entry_block :note="$note->note" />
|
||||
@endforeach
|
||||
|
||||
<hr>
|
||||
|
||||
@foreach ($notes as $note)
|
||||
<x-blog_entry_block :note="$note" />
|
||||
@endforeach
|
||||
|
||||
{{ $notes->links () }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -53,5 +53,7 @@
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ $notes->links("pagination::default") }}
|
||||
</div>
|
||||
@endsection
|
||||
|
20
resources/views/components/blog_entry_block.blade.php
Normal file
20
resources/views/components/blog_entry_block.blade.php
Normal file
@ -0,0 +1,20 @@
|
||||
<div class="entry">
|
||||
<p class="publish-date">
|
||||
<time class="ago">
|
||||
{{ $note->created_at->diffForHumans () }}
|
||||
</time>
|
||||
</p>
|
||||
|
||||
<div class="inner">
|
||||
<h3 class="title">
|
||||
<a href="{{ route ('posts.show', [ 'note' => $note ]) }}">
|
||||
{{ $note->summary }}
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
<a href="{{ route ('posts.show', [ 'note' => $note ]) }}">
|
||||
» View Blog Entry
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -17,10 +17,15 @@ if (!$actor)
|
||||
return;
|
||||
}
|
||||
|
||||
if ($actor->user_id)
|
||||
if ($actor->blog_id)
|
||||
$actor_url = route ('blogs.show', [ 'blog' => $actor->blog->slug ]);
|
||||
else 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 ]);
|
||||
|
||||
if (!$display_post->can_view ())
|
||||
return;
|
||||
@endphp
|
||||
|
||||
<tr>
|
||||
@ -32,11 +37,7 @@ else
|
||||
</a>
|
||||
<a href="{{ $actor_url }}">
|
||||
<p>
|
||||
@if ($actor->user)
|
||||
<img loading="lazy" src="{{ $actor->user->avatar }}" class="pfp-fallback" width="50">
|
||||
@else
|
||||
<img loading="lazy" src="{{ $actor->icon }}" class="pfp-fallback" width="50">
|
||||
@endif
|
||||
</p>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -10,9 +10,18 @@
|
||||
<br>
|
||||
|
||||
<textarea name="content" placeholder="What's on your mind?" style="width: 100%"></textarea>
|
||||
<input type="file" name="files[]" accept="image/*" multiple><br>
|
||||
<button type="submit">Post</button>
|
||||
<small>Markdown is supported</small>
|
||||
<br>
|
||||
<input type="file" name="files[]" accept="image/*" multiple><br>
|
||||
<div>
|
||||
<b>Visibility:</b>
|
||||
<select name="visibility">
|
||||
<option value="public">Public</option>
|
||||
<option value="followers">Friends only</option>
|
||||
<option value="private">Mentioned Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit">Post</button>
|
||||
|
||||
@error ("content")
|
||||
<div class="error">{{ $message }}</div>
|
||||
|
@ -25,11 +25,11 @@
|
||||
View My
|
||||
<a href="{{ route('users.show', ['user_name' => auth()->user()->name]) }}">Profile</a>
|
||||
|
|
||||
<a href="#">Blog</a>
|
||||
<a href="{{ route ('users.blogs', [ 'user_name' => auth ()->user ()->name ]) }}">Blog</a>
|
||||
|
|
||||
<a href="#">Bulletins</a>
|
||||
|
|
||||
<a href="#">Friends</a>
|
||||
<a href="{{ route ('users.friends', [ 'user_name' => auth ()->user ()->name ]) }}">Friends</a>
|
||||
</p>
|
||||
<p>
|
||||
My URL:
|
||||
|
@ -47,7 +47,11 @@
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="#"> Blog </a>
|
||||
@auth
|
||||
<a href="{{ route ('blogs') }}"> Blog </a>
|
||||
@else
|
||||
<a href="{{ route ('login') }}"> Blog</a>
|
||||
@endauth
|
||||
</li>
|
||||
|
||||
<li>
|
||||
@ -66,6 +70,12 @@
|
||||
<a href="#"> Favs </a>
|
||||
</li>
|
||||
|
||||
@auth
|
||||
<li class="active">
|
||||
<a href="{{ route ('users.notifications') }}"> Notifications ({{ count (auth ()->user ()->unreadNotifications) }})</a>
|
||||
</li>
|
||||
@endauth
|
||||
|
||||
<li>
|
||||
<a href="https://github.com/0xd011f4ce/OurSpace"> Source </a>
|
||||
</li>
|
||||
|
@ -44,10 +44,7 @@
|
||||
|
||||
@if (auth ()->check ())
|
||||
<script>
|
||||
const notification_sound = new Audio ("/resources/sounds/notification.mp3")
|
||||
notification_sound.muted = true
|
||||
notification_sound.play ()
|
||||
notification_sound.muted = false
|
||||
const notification_sound = new Audio ("{{ auth ()->user ()->notification_sound }}")
|
||||
|
||||
function register_echo ()
|
||||
{
|
||||
|
@ -1,20 +1,38 @@
|
||||
@php
|
||||
$user_url = null;
|
||||
|
||||
if ($actor->blog_id)
|
||||
{
|
||||
$user_url = route ('blogs.show', [ 'blog' => $actor->blog->slug ]);
|
||||
}
|
||||
else if ($actor->user_id)
|
||||
{
|
||||
$user_url = route ('users.show', [ 'user_name' => $actor->user->name ]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$user_url = route ('users.show', [ 'user_name' => $actor->local_actor_id ]);
|
||||
}
|
||||
@endphp
|
||||
|
||||
@extends("partials.layout")
|
||||
|
||||
@section ("title", "View Post")
|
||||
|
||||
@section ("content")
|
||||
|
||||
<div class="row article blog-entry">
|
||||
<div class="col w-20 left">
|
||||
<div class="edit-info">
|
||||
<div class="profile-pic">
|
||||
<img loading="lazy" src="{{ $actor->user ? $actor->user->avatar : $actor->icon }}" class="pfp-fallback">
|
||||
<img loading="lazy" src="{{ $actor->icon }}" class="pfp-fallback">
|
||||
</div>
|
||||
|
||||
<div class="author-details">
|
||||
<h4>
|
||||
Published by
|
||||
<span>
|
||||
<a href="{{ route ('users.show', [ 'user_name' => $actor->user ? $actor->user->name : $actor->local_actor_id ]) }}">
|
||||
<a href="{{ $user_url }}">
|
||||
{{ $actor->name }}
|
||||
</a>
|
||||
</span>
|
||||
@ -28,9 +46,9 @@
|
||||
</p>
|
||||
|
||||
<p class="links">
|
||||
<a href="{{ route ('users.show', [ 'user_name' => $actor->user ? $actor->user->name : $actor->local_actor_id ]) }}">
|
||||
<a href="{{ $user_url }}">
|
||||
<img loading="lazy" src="/resources/icons/user.png" class="icon">
|
||||
<span class="m-hide">View</span> Profile
|
||||
<span class="m-hide">View</span> {{ $actor->blog_id ? "Blog" : "Profile" }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
20
resources/views/posts/tag.blade.php
Normal file
20
resources/views/posts/tag.blade.php
Normal file
@ -0,0 +1,20 @@
|
||||
@extends ("partials.layout")
|
||||
|
||||
@section ("title", "Posts Tagged with " . $hashtag->name)
|
||||
|
||||
@section ("content")
|
||||
<div class="simple-container">
|
||||
<h1>Showing posts with tag: {{ $hashtag->name }}</h1>
|
||||
<br>
|
||||
|
||||
<table class="comments-table" cellspacing="0" cellpadding="3" bordercollor="#ffffff" border="1">
|
||||
<tbody>
|
||||
@foreach ($posts as $post)
|
||||
<x-comment_block :post="$post" />
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ $posts->links("pagination::default") }}
|
||||
</div>
|
||||
@endsection
|
42
resources/views/users/blogs.blade.php
Normal file
42
resources/views/users/blogs.blade.php
Normal file
@ -0,0 +1,42 @@
|
||||
@extends ("partials.layout")
|
||||
|
||||
@section ("title", $user->name . "'s Blogs")
|
||||
|
||||
@section ("content")
|
||||
<div class="simple-container">
|
||||
<h1>{{ $user->name }}'s Blogs</h1>
|
||||
<p>
|
||||
<a href="{{ route ('users.show', [ 'user_name' => $user->name ]) }}">« Back to profile</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="blog-entries">
|
||||
@foreach ($blogs as $blog)
|
||||
<div class="entry">
|
||||
<p class="publish-date">
|
||||
<time class="ago">{{ $blog->created_at->diffForHumans () }}</time>
|
||||
— by <a href="{{ route ('users.show', [ 'user_name' => $blog->user->name ]) }}">{{ $blog->user->name }}</a>
|
||||
— <a>{{ count ($blog->notes) }} Posts</a>
|
||||
</p>
|
||||
|
||||
<div class="inner">
|
||||
<h3 class="title">
|
||||
<a href="{{ route ('blogs.show', [ 'blog' => $blog ]) }}">
|
||||
{{ $blog->name }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
{!! $blog->description !!}
|
||||
</p>
|
||||
|
||||
<a href="{{ route ('blogs.show', [ 'blog' => $blog ]) }}">
|
||||
» Read more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -36,8 +36,14 @@
|
||||
@error("song")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
<small>Max file size: 4MB</small>
|
||||
<br><br>
|
||||
<small>Select custom notification sound:</small>
|
||||
<input type="file" name="notification_sound" accept="audio/*"><br>
|
||||
@error("notification_sound")
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
<small>Max file size: 1MB</small>
|
||||
<br>
|
||||
<h1>Bio:</h1>
|
||||
<br>
|
||||
<textarea name="bio" id="bio" cols="58" placeholder="Bio">{{ $user->bio }}</textarea>
|
||||
|
71
resources/views/users/notifications.blade.php
Normal file
71
resources/views/users/notifications.blade.php
Normal file
@ -0,0 +1,71 @@
|
||||
@extends ("partials.layout")
|
||||
|
||||
@section ("title", "Notifications")
|
||||
|
||||
@section ("content")
|
||||
<div class="simple-container">
|
||||
<h1>Notifications</h1>
|
||||
<p>You have <b>{{ $unread_notifications }}</b> unread notifications</p>
|
||||
|
||||
<table border="1" width="100%">
|
||||
<tr>
|
||||
<th style="width: 100px">Actor</th>
|
||||
<th>Content</th>
|
||||
<th>Time</th>
|
||||
<th>Read</th>
|
||||
</tr>
|
||||
|
||||
@foreach ($processed_notifications as $notification)
|
||||
@if (!$notification ['actor'] || !$notification ['object'])
|
||||
@continue
|
||||
@endif
|
||||
|
||||
<tr @if ($notification ['read_at'] == null) style="font-weight: bold" @endif>
|
||||
<td>
|
||||
@if ($notification ['type'] == 'Signup')
|
||||
<a href="{{ route ('users.show', [ 'user_name' => $notification ['object']->actor->preferredUsername ]) }}">
|
||||
<p>{{ $notification ['object']->actor->preferredUsername }}</p>
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route ('users.show', [ 'user_name' => $notification ['actor']->local_actor_id ? $notification ['actor']->local_actor_id : $notification ['actor']->name ]) }}">
|
||||
<p>{{ $notification ['actor']->name }}</p>
|
||||
</a>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if ($notification ['type'] == 'Signup')
|
||||
<p>Joined Ourspace! Say something!!</p>
|
||||
@elseif ($notification ['type'] == 'Follow')
|
||||
<p>Followed you</p>
|
||||
@elseif ($notification ['type'] == 'Unfollow')
|
||||
@if ($notification ["object"]->id == auth ()->user ()->id)
|
||||
<p>You unfollowed you</p>
|
||||
@else
|
||||
<p>Unfollowed <b>{{ $notification ['object']->name }}</b></p>
|
||||
@endif
|
||||
@elseif ($notification ['type'] == 'Boost')
|
||||
<p>Boosted this <b><a href="{{ route ('posts.show', ['note' => $notification['object']->id]) }}">post</a></b></p>
|
||||
@elseif ($notification ['type'] == 'Like')
|
||||
<p>Liked this <b><a href="{{ route ('posts.show', ['note' => $notification['object']->id]) }}">post</a></b></p>
|
||||
@elseif ($notification ['type'] == 'Reply')
|
||||
<p>Replied to this <b><a href="{{ route ('posts.show', ['note' => $notification['object']->id]) }}">post</a></b></p>
|
||||
@elseif ($notification ['type'] == 'Mention')
|
||||
<p>Mentioned you in this <b><a href="{{ route ('posts.show', ['note' => $notification['object']->id])}}">post</a></b></p>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<p>{{ $notification ['created_at']->diffForHumans () }}</p>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="checkbox" @if ($notification ['read_at'] != null) checked @endif disabled>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
|
||||
{{ $notifications->links ("pagination::default") }}
|
||||
</div>
|
||||
@endsection
|
@ -40,7 +40,7 @@
|
||||
|
||||
<script>
|
||||
let music = document.getElementById ('music');
|
||||
music.volume = 0.1;
|
||||
music.volume = 0.075;
|
||||
music.play ()
|
||||
</script>
|
||||
@endif
|
||||
@ -50,7 +50,7 @@
|
||||
<div class="mood">
|
||||
@if ($user != null)
|
||||
<p><b>Mood:</b> {{ $user->mood }}</p>
|
||||
<p><b>View my: <a href="#">Blog</a> | <a href="#">Bulletins</a></b></p>
|
||||
<p><b>View my: <a href="{{ route ('users.blogs', [ 'user_name' => $user->name ]) }}">Blog</a> | <a href="#">Bulletins</a></b></p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@ -280,10 +280,9 @@
|
||||
@if ($user != null)
|
||||
<div class="blog-preview">
|
||||
<h4>
|
||||
{{ $user->name }}'s Latest Blog Entries [<a href="#">View Blog</a>]
|
||||
{{ $user->name }}'s Latest Blog Entries [<a href="{{ route ('users.blogs', [ 'user_name' => $user->name ]) }}">View Blog</a>]
|
||||
</h4>
|
||||
<p>
|
||||
<i>There are no Blog Entries yet.</i>
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
@ -19,15 +19,16 @@ Route::get ("/.well-known/nodeinfo/2.1", [ APNodeInfoController::class, "nodeinf
|
||||
|
||||
Route::prefix ("/ap/v1")->group (function () {
|
||||
// users
|
||||
Route::post ("/user/{user:name}/inbox", [ APInboxController::class, "inbox" ])->name ("ap.inbox");
|
||||
Route::post ("/user/{user:name}/outbox", [ APOutboxController::class, "outbox" ])->name ("ap.outbox");
|
||||
Route::get ("/user/{user:name}/followers", [ APActorController::class, "followers" ])->name ("ap.followers");
|
||||
Route::get ("/user/{user:name}/following", [ APActorController::class, "following" ])->name ("ap.following");
|
||||
Route::get ("/user/{user:name}/collections/featured", [ APActorController::class, "featured" ])->name ("ap.featured");
|
||||
Route::get ("/user/{user:name}", [ APActorController::class, "user" ])->name ("ap.user");
|
||||
Route::post ("/user/{name}/inbox", [ APInboxController::class, "inbox" ])->name ("ap.inbox");
|
||||
Route::post ("/user/{name}/outbox", [ APOutboxController::class, "outbox" ])->name ("ap.outbox");
|
||||
Route::get ("/user/{name}/followers", [ APActorController::class, "followers" ])->name ("ap.followers");
|
||||
Route::get ("/user/{name}/following", [ APActorController::class, "following" ])->name ("ap.following");
|
||||
Route::get ("/user/{name}/collections/featured", [ APActorController::class, "featured" ])->name ("ap.featured");
|
||||
Route::get ("/user/{name}", [ APActorController::class, "user" ])->name ("ap.user");
|
||||
|
||||
// notes
|
||||
Route::get ("/note/{note:private_id}", [ APGeneralController::class, "note" ])->name ("ap.note");
|
||||
|
||||
// instance
|
||||
Route::post ("/inbox", [ APInstanceInboxController::class, "inbox" ])->name ("ap.inbox");
|
||||
});
|
||||
|
@ -7,6 +7,7 @@ use App\Http\Controllers\PostController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\UserActionController;
|
||||
use App\Http\Controllers\BlogController;
|
||||
|
||||
// auth related
|
||||
Route::get ("/auth/login", [ UserController::class, "login" ])->name ("login")->middleware ("guest");
|
||||
@ -26,7 +27,9 @@ Route::middleware ("update_online")->group (function () {
|
||||
// user routes
|
||||
Route::get ("/user/edit", [ ProfileController::class, "edit" ])->name ("users.edit")->middleware ("auth");
|
||||
Route::post ("/user/edit", [ ProfileController::class, "update" ])->middleware ("auth");
|
||||
Route::get ("/user/notifications", [ ProfileController::class, "notifications" ])->name ("users.notifications")->middleware ("auth");
|
||||
Route::get ("/user/{user_name}/friends", [ ProfileController::class, "friends" ])->name ("users.friends");
|
||||
Route::get ("/user/{user_name}/blogs", [ ProfileController::class, "blogs" ])->name ("users.blogs");
|
||||
Route::get ("/user/{user_name}", [ ProfileController::class, "show" ])->name ("users.show");
|
||||
|
||||
// posts routes
|
||||
@ -41,10 +44,17 @@ Route::middleware ("update_online")->group (function () {
|
||||
// other routes
|
||||
Route::get ("/browse", [ HomeController::class, "browse" ])->name ("browse");
|
||||
Route::get ("/search", [ HomeController::class, "search" ])->name ("search");
|
||||
Route::get ("/tags/{tag}", [ HomeController::class, "tag" ])->name ("tags"); // TODO: This
|
||||
Route::get ("/tags/{tag}", [ HomeController::class, "tag" ])->name ("tags");
|
||||
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");
|
||||
|
||||
// blog routes
|
||||
Route::get ("/blogs/create", [ BlogController::class, "create" ])->name ("blogs.create")->middleware ("auth");
|
||||
Route::post ("/blogs/create", [ BlogController::class, "store" ])->middleware ("auth");
|
||||
Route::get ("/blogs/{blog:slug}/entry/new", [ BlogController::class, "new_entry" ])->name ("blogs.new_entry")->middleware("auth");
|
||||
Route::get ("/blogs/{blog:slug}", [ BlogController::class, "show" ])->name ("blogs.show");
|
||||
Route::get ("/blogs", [ BlogController::class, "index" ])->name ("blogs");
|
||||
});
|
||||
|
||||
require __DIR__ . "/api.php";
|
||||
|
Loading…
x
Reference in New Issue
Block a user