Commit 999999e3 authored by Ola Hneini's avatar Ola Hneini Committed by Yassine Doghri
Browse files

feat: add notifications inbox for actors

closes #215
parent c29c018c
Loading
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

/**
 * Class AddNotifications Creates notifications table in database
 *
 * @copyright  2021 Ad Aures
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class AddNotifications extends Migration
{
    public function up(): void
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'actor_id' => [
                'type' => 'INT',
                'unsigned' => true,
            ],
            'target_actor_id' => [
                'type' => 'INT',
                'unsigned' => true,
            ],
            'post_id' => [
                'type' => 'BINARY',
                'constraint' => 16,
                'null' => true,
            ],
            'activity_id' => [
                'type' => 'BINARY',
                'constraint' => 16,
            ],
            'type' => [
                'type' => 'ENUM',
                'constraint' => ['like', 'follow', 'share', 'reply'],
            ],
            'read_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
            'created_at' => [
                'type' => 'DATETIME',
            ],
            'updated_at' => [
                'type' => 'DATETIME',
            ],
        ]);

        $tablesPrefix = config('Fediverse')
            ->tablesPrefix;

        $this->forge->addPrimaryKey('id');
        $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
        $this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
        $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
        $this->forge->addForeignKey('activity_id', $tablesPrefix . 'activities', 'id', '', 'CASCADE');
        $this->forge->createTable('notifications');
    }

    public function down(): void
    {
        $this->forge->dropTable('notifications');
    }
}
+62 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

/**
 * Class AddActivitiesTrigger Creates activities trigger in database
 *
 * @copyright  2020 Ad Aures
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class AddNotificationsTrigger extends Migration
{
    public function up(): void
    {
        $activitiesTable = $this->db->prefixTable(config('Fediverse')->tablesPrefix . 'activities');
        $notificationsTable = $this->db->prefixTable('notifications');
        $createQuery = <<<CODE_SAMPLE
        CREATE TRIGGER `{$activitiesTable}_after_insert`
        AFTER INSERT ON `{$activitiesTable}`
        FOR EACH ROW
        BEGIN
        -- only create notification if new incoming activity with NULL status is created
        IF NEW.target_actor_id AND NEW.target_actor_id != NEW.actor_id AND NEW.status IS NULL THEN
            IF NEW.type IN ( 'Create', 'Like', 'Announce', 'Follow' ) THEN
                SET @type = (CASE
                                WHEN NEW.type = 'Create' THEN 'reply'
                                WHEN NEW.type = 'Like' THEN 'like'
                                WHEN NEW.type = 'Announce' THEN 'share'
                                WHEN NEW.type = 'Follow' THEN 'follow'
                            END);
                INSERT INTO `{$notificationsTable}` (`actor_id`, `target_actor_id`, `post_id`, `activity_id`, `type`, `created_at`, `updated_at`)
                    VALUES (NEW.actor_id, NEW.target_actor_id, NEW.post_id, NEW.id, @type, NEW.created_at, NEW.created_at);
            ELSE
                DELETE FROM `{$notificationsTable}`
                WHERE `actor_id` = NEW.actor_id
                AND `target_actor_id` = NEW.target_actor_id
                AND ((`type` = (CASE WHEN NEW.type = 'Undo_Follow' THEN 'follow' END) AND `post_id` IS NULL)
                    OR (`type` = (CASE
                                    WHEN NEW.type = 'Delete' THEN 'reply'
                                    WHEN NEW.type = 'Undo_Like' THEN 'like'
                                    WHEN NEW.type = 'Undo_Announce' THEN 'share'
                                END)
                        AND `post_id` = NEW.post_id));
            END IF;
        END IF;
        END
        CODE_SAMPLE;
        $this->db->query($createQuery);
    }

    public function down(): void
    {
        $activitiesTable = $this->db->prefixTable(config('Fediverse')->tablesPrefix . 'activities');
        $this->db->query("DROP TRIGGER IF EXISTS `{$activitiesTable}_after_insert`");
    }
}
+106 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

/**
 * @copyright  2020 Ad Aures
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

namespace App\Entities;

use Michalsn\Uuid\UuidEntity;
use Modules\Fediverse\Entities\Activity;
use Modules\Fediverse\Entities\Actor;
use Modules\Fediverse\Entities\Post;
use Modules\Fediverse\Models\ActorModel;
use Modules\Fediverse\Models\PostModel;
use RuntimeException;

/**
 * @property int $id
 * @property int $actor_id
 * @property Actor $actor
 * @property int $target_actor_id
 * @property Actor $target_actor
 * @property string|null $post_id
 * @property Post $post
 * @property string $activity_id
 * @property Activity $activity
 * @property 'like'|'follow'|'share'|'reply' $type
 * @property Time|null $read_at
 * @property Time $created_at
 * @property Time $updated_at
 */
class Notification extends UuidEntity
{
    protected ?Actor $actor = null;

    protected ?Actor $target_actor = null;

    protected ?Post $post = null;

    protected ?Activity $activity = null;

    /**
     * @var string[]
     */
    protected $uuids = ['post_id', 'activity_id'];

    /**
     * @var string[]
     */
    protected $dates = ['read_at', 'created_at', 'updated_at'];

    /**
     * @var array<string, string>
     */
    protected $casts = [
        'id' => 'integer',
        'actor_id' => 'integer',
        'target_actor_id' => 'integer',
        'post_id' => '?string',
        'activity_id' => 'string',
        'type' => 'string',
    ];

    public function getActor(): ?Actor
    {
        if ($this->actor_id === null) {
            throw new RuntimeException('Notification must have an actor_id before getting actor.');
        }

        if (! $this->actor instanceof Actor) {
            $this->actor = (new ActorModel())->getActorById($this->actor_id);
        }

        return $this->actor;
    }

    public function getTargetActor(): ?Actor
    {
        if ($this->target_actor_id === null) {
            throw new RuntimeException('Notification must have a target_actor_id before getting target actor.');
        }

        if (! $this->target_actor instanceof Actor) {
            $this->target_actor = (new ActorModel())->getActorById($this->target_actor_id);
        }

        return $this->target_actor;
    }

    public function getPost(): ?Post
    {
        if ($this->post_id === null) {
            throw new RuntimeException('Notification must have a post_id before getting post.');
        }

        if (! $this->post instanceof Post) {
            $this->post = (new PostModel())->getPostById($this->post_id);
        }

        return $this->post;
    }
}
+47 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

/**
 * @copyright  2021 Ad Aures
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

namespace App\Models;

use App\Entities\Notification;
use Michalsn\Uuid\UuidModel;

class NotificationModel extends UuidModel
{
    /**
     * @var string
     */
    protected $table = 'notifications';

    /**
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * @var string
     */
    protected $returnType = Notification::class;

    /**
     * @var bool
     */
    protected $useTimestamps = true;

    /**
     * @var string[]
     */
    protected $uuidFields = ['post_id', 'activity_id'];

    /**
     * @var string[]
     */
    protected $allowedFields = ['read_at'];
}
+6 −0
Original line number Diff line number Diff line
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <g>
        <path fill="none" d="M0 0h24v24H0z"/>
        <path d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0v7zM9 21h6v2H9v-2z"/>
    </g>
</svg>
 No newline at end of file
Loading