From c83ae76a689a101e1fd5841ac0f7301f1674154e Mon Sep 17 00:00:00 2001 From: Eliseu Amaro Date: Fri, 10 Dec 2021 18:23:03 +0000 Subject: [PATCH] [COMPONENTS][Conversation] Conversation entity moved to respective component, URI column added Route for conversation added and Conversation Controller created. [CONTROLLER][Conversation] Created ConversationShow function, will be used to render the conversation route page [ENTITY][Note] Conversation id column added, this way a Note can have a direct relation with its respective conversation. --- .../Conversation/Controller/Conversation.php | 2 + components/Conversation/Controller/Reply.php | 14 +-- components/Conversation/Conversation.php | 1 + .../Conversation/Entity/Conversation.php | 108 ++++++++++++++++++ src/Entity/Note.php | 104 +++++++++-------- 5 files changed, 174 insertions(+), 55 deletions(-) create mode 100644 components/Conversation/Entity/Conversation.php diff --git a/components/Conversation/Controller/Conversation.php b/components/Conversation/Controller/Conversation.php index 143782533c..00c7e8e3af 100644 --- a/components/Conversation/Controller/Conversation.php +++ b/components/Conversation/Controller/Conversation.php @@ -26,6 +26,7 @@ declare(strict_types = 1); namespace Component\Conversation\Controller; +use _PHPStan_76800bfb5\Nette\NotImplementedException; use App\Core\Controller\FeedController; use App\Core\DB\DB; use App\Core\Form; @@ -55,6 +56,7 @@ class Conversation extends FeedController // if note is a reply -> link from above plus anchor public function ConversationShow(Request $request) { + throw new NotImplementedException(); $actor_id = Common::ensureLoggedIn()->getId(); $notes = DB::dql('select n from App\Entity\Note n ' . 'where n.reply_to is not null and n.actor_id = :id ' diff --git a/components/Conversation/Controller/Reply.php b/components/Conversation/Controller/Reply.php index 7d03aa0ba8..ad72deaddf 100644 --- a/components/Conversation/Controller/Reply.php +++ b/components/Conversation/Controller/Reply.php @@ -40,7 +40,6 @@ use App\Util\Exception\DuplicateFoundException; use App\Util\Exception\InvalidFormException; use App\Util\Exception\NoLoggedInUser; use App\Util\Exception\NoSuchNoteException; -use App\Util\Exception\NotImplementedException; use App\Util\Exception\RedirectException; use App\Util\Exception\ServerException; use App\Util\Form\FormFields; @@ -143,20 +142,15 @@ class Reply extends FeedController public function replies(Request $request) { - // TODO replies - throw new NotImplementedException; $actor_id = Common::ensureLoggedIn()->getId(); $notes = DB::dql('select n from App\Entity\Note n ' . 'where n.reply_to is not null and n.actor_id = :id ' . 'order by n.created DESC', ['id' => $actor_id], ); - - $notes_out = null; - Event::handle('FormatNoteList', [$notes, &$notes_out]); - - return $this->process_feed([ + return [ '_template' => 'feeds/feed.html.twig', - 'notes' => $notes_out, + 'notes' => $notes, + 'should_format' => false, 'page_title' => 'Replies feed', - ]); + ]; } } diff --git a/components/Conversation/Conversation.php b/components/Conversation/Conversation.php index f161982c86..c0523d4eed 100644 --- a/components/Conversation/Conversation.php +++ b/components/Conversation/Conversation.php @@ -132,6 +132,7 @@ class Conversation extends Component { $r->connect('reply_add', '/object/note/{id<\d+>}/reply', [ReplyController::class, 'replyAddNote']); $r->connect('replies', '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/replies', [ReplyController::class, 'replies']); + $r->connect('conversation', '/conversation/{id<\d+>}', [ReplyController::class, 'conversation']); return Event::next; } diff --git a/components/Conversation/Entity/Conversation.php b/components/Conversation/Entity/Conversation.php new file mode 100644 index 0000000000..c1441a03a1 --- /dev/null +++ b/components/Conversation/Entity/Conversation.php @@ -0,0 +1,108 @@ +. + +// }}} + +namespace Component\Conversation\Entity; + +use App\Core\DB\DB; +use App\Core\Entity; +use App\Entity\Note; + +/** + * Entity class for Conversations + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @author Mikael Nordfeldth + * @copyright 2010 StatusNet Inc. + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @author Eliseu Amaro + * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class Conversation extends Entity +{ + // {{{ Autocode + // @codeCoverageIgnoreStart + private int $id; + private string $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space + private int $note_id; + + public function setId(int $id): self + { + $this->id = $id; + return $this; + } + + public function getId(): int + { + return $this->id; + } + + public function setUri(string $uri): self + { + $this->uri = $uri; + return $this; + } + + public function getUri(): string + { + return $this->uri; + } + + public function setNoteId(int $note_id): self + { + $this->note_id = $note_id; + return $this; + } + + public function getNoteId(): int + { + return $this->note_id; + } + + + // @codeCoverageIgnoreEnd + // }}} Autocode + + public static function schemaDef(): array + { + return [ + 'name' => 'conversation', + 'fields' => [ + 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'Serial identifier, since any additional meaning would require updating its value for every reply upon receiving a new aparent root'], + 'uri' => ['type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'URI of the conversation'], + 'note_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'Root of note for this conversation'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'conversation_uri_uniq' => ['uri'], + ], + 'foreign keys' => [ + 'note_id_to_id_fkey' => ['note', ['note_id' => 'id']], + ], + ]; + } +} diff --git a/src/Entity/Note.php b/src/Entity/Note.php index 6a4e247ed1..e056fdc99f 100644 --- a/src/Entity/Note.php +++ b/src/Entity/Note.php @@ -48,17 +48,18 @@ class Note extends Entity // @codeCoverageIgnoreStart private int $id; private int $actor_id; - private ?string $content_type = null; - private ?string $content = null; - private ?string $rendered = null; + private ?string $content; + private string $content_type = 'text/plain'; + private ?string $rendered; + private int $conversation_id; private ?int $reply_to; private bool $is_local; private ?string $source; - private int $scope = VisibilityScope::PUBLIC; - private string $url; - private ?int $language_id = null; - private DateTimeInterface $created; - private DateTimeInterface $modified; + private int $scope = 1; + private ?string $url; + private ?int $language_id; + private \DateTimeInterface $created; + private \DateTimeInterface $modified; public function setId(int $id): self { @@ -82,17 +83,6 @@ class Note extends Entity return $this->actor_id; } - public function getContentType(): string - { - return $this->content_type; - } - - public function setContentType(string $content_type): self - { - $this->content_type = $content_type; - return $this; - } - public function setContent(?string $content): self { $this->content = $content; @@ -104,6 +94,17 @@ class Note extends Entity return $this->content; } + public function setContentType(string $content_type): self + { + $this->content_type = $content_type; + return $this; + } + + public function getContentType(): string + { + return $this->content_type; + } + public function setRendered(?string $rendered): self { $this->rendered = $rendered; @@ -115,6 +116,17 @@ class Note extends Entity return $this->rendered; } + public function setConversationId(int $conversation_id): self + { + $this->conversation_id = $conversation_id; + return $this; + } + + public function getConversationId(): int + { + return $this->conversation_id; + } + public function setReplyTo(?int $reply_to): self { $this->reply_to = $reply_to; @@ -123,7 +135,7 @@ class Note extends Entity public function getReplyTo(): ?int { - return $this->reply_to ?: null; + return $this->reply_to; } public function setIsLocal(bool $is_local): self @@ -159,20 +171,15 @@ class Note extends Entity return $this->scope; } - public function getUrl(): string - { - return $this->url; - } - - public function setUrl(string $url): self + public function setUrl(?string $url): self { $this->url = $url; return $this; } - public function getLanguageId(): ?int + public function getUrl(): ?string { - return $this->language_id; + return $this->url; } public function setLanguageId(?int $language_id): self @@ -181,28 +188,34 @@ class Note extends Entity return $this; } - public function setCreated(DateTimeInterface $created): self + public function getLanguageId(): ?int + { + return $this->language_id; + } + + public function setCreated(\DateTimeInterface $created): self { $this->created = $created; return $this; } - public function getCreated(): DateTimeInterface + public function getCreated(): \DateTimeInterface { return $this->created; } - public function setModified(DateTimeInterface $modified): self + public function setModified(\DateTimeInterface $modified): self { $this->modified = $modified; return $this; } - public function getModified(): DateTimeInterface + public function getModified(): \DateTimeInterface { return $this->modified; } + // @codeCoverageIgnoreEnd // }}} Autocode @@ -420,19 +433,20 @@ class Note extends Entity return [ 'name' => 'note', 'fields' => [ - 'id' => ['type' => 'serial', 'not null' => true], - 'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'who made the note'], - 'content' => ['type' => 'text', 'description' => 'note content'], - 'content_type' => ['type' => 'varchar', 'not null' => true, 'default' => 'text/plain', 'length' => 129, 'description' => 'A note can be written in a multitude of formats such as text/plain, text/markdown, application/x-latex, and text/html'], - 'rendered' => ['type' => 'text', 'description' => 'rendered note content, so we can keep the microtags (if not local)'], - 'reply_to' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'note replied to, null if root of a conversation'], - 'is_local' => ['type' => 'bool', 'not null' => true, 'description' => 'was this note generated by a local actor'], - 'source' => ['type' => 'varchar', 'foreign key' => true, 'length' => 32, 'target' => 'NoteSource.code', 'multiplicity' => 'many to one', 'description' => 'fkey to source of note, like "web", "im", or "clientname"'], - 'scope' => ['type' => 'int', 'not null' => true, 'default' => VisibilityScope::PUBLIC, 'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = groups; 8 = subscribers; 16 = messages; null = default'], - 'url' => ['type' => 'text', 'description' => 'Permalink to Note'], - 'language_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Language.id', 'multiplicity' => 'one to many', 'description' => 'The language for this note'], - 'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'], - 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + 'id' => ['type' => 'serial', 'not null' => true], + 'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'who made the note'], + 'content' => ['type' => 'text', 'description' => 'note content'], + 'content_type' => ['type' => 'varchar', 'not null' => true, 'default' => 'text/plain', 'length' => 129, 'description' => 'A note can be written in a multitude of formats such as text/plain, text/markdown, application/x-latex, and text/html'], + 'rendered' => ['type' => 'text', 'description' => 'rendered note content, so we can keep the microtags (if not local)'], + 'conversation_id' => ['type' => 'serial', 'not null' => true, 'foreign key' => true, 'target' => 'Conversation.id', 'multiplicity' => 'one to one', 'description' => 'the conversation identifier'], + 'reply_to' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'note replied to, null if root of a conversation'], + 'is_local' => ['type' => 'bool', 'not null' => true, 'description' => 'was this note generated by a local actor'], + 'source' => ['type' => 'varchar', 'foreign key' => true, 'length' => 32, 'target' => 'NoteSource.code', 'multiplicity' => 'many to one', 'description' => 'fkey to source of note, like "web", "im", or "clientname"'], + 'scope' => ['type' => 'int', 'not null' => true, 'default' => VisibilityScope::PUBLIC, 'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = groups; 8 = subscribers; 16 = messages; null = default'], + 'url' => ['type' => 'text', 'description' => 'Permalink to Note'], + 'language_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Language.id', 'multiplicity' => 'one to many', 'description' => 'The language for this note'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'], + 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], ], 'primary key' => ['id'], 'indexes' => [