diff --git a/.env b/.env index 29f1c31..74b141d 100644 --- a/.env +++ b/.env @@ -42,3 +42,9 @@ MERCURE_PUBLIC_URL=https://${SERVER_NAME}/.well-known/mercure # The secret used to sign the JWTs MERCURE_JWT_SECRET="!NotSoSecretMercureHubJWTSecretKey!" ###< symfony/mercure-bundle ### +###> elastic ### +ELASTICSEARCH_HOST=localhost +ELASTICSEARCH_PORT=9200 +ELASTICSEARCH_USERNAME=elastic +ELASTICSEARCH_PASSWORD=your_password +###< elastic ### diff --git a/assets/icons/iconoir/search.svg b/assets/icons/iconoir/search.svg new file mode 100644 index 0000000..eca1df3 --- /dev/null +++ b/assets/icons/iconoir/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/styles/app.css b/assets/styles/app.css index f32a1fa..4431759 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -370,3 +370,9 @@ footer p { height: 400px; margin-bottom: 20px; } + +/* Search */ +label.search { + width: 100%; + justify-content: center; +} diff --git a/config/bundles.php b/config/bundles.php index 250549d..181adf4 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -14,4 +14,5 @@ return [ Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['all' => true, 'prod' => false], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\UX\Icons\UXIconsBundle::class => ['all' => true], + FOS\ElasticaBundle\FOSElasticaBundle::class => ['all' => true], ]; diff --git a/config/packages/fos_elastica.yaml b/config/packages/fos_elastica.yaml new file mode 100644 index 0000000..316fd1f --- /dev/null +++ b/config/packages/fos_elastica.yaml @@ -0,0 +1,23 @@ +fos_elastica: + clients: + default: + host: '%env(ELASTICSEARCH_HOST)%' + port: '%env(int:ELASTICSEARCH_PORT)%' + username: '%env(ELASTICSEARCH_USERNAME)%' + password: '%env(ELASTICSEARCH_PASSWORD)%' + indexes: + # create the index by running php bin/console fos:elastica:populate + articles: + properties: + title: ~ + summary: ~ + content: ~ + slug: ~ + topics: ~ + persistence: + driver: orm + model: App\Entity\Article + provider: ~ + listener: ~ + finder: ~ + diff --git a/config/services.yaml b/config/services.yaml index 97fe0f0..7fde9a1 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -26,3 +26,7 @@ services: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler: arguments: - '%env(DATABASE_URL)%' + + # + FOS\ElasticaBundle\Finder\FinderInterface: + alias: fos_elastica.finder.articles diff --git a/src/Controller/AuthorController.php b/src/Controller/AuthorController.php index 7f08678..484e7b0 100644 --- a/src/Controller/AuthorController.php +++ b/src/Controller/AuthorController.php @@ -26,7 +26,7 @@ class AuthorController extends AbstractController public function index($npub, EntityManagerInterface $entityManager, NostrClient $client): Response { $meta = $client->getNpubMetadata($npub); - $author = (array) json_decode($meta->content); + $author = (array) json_decode($meta->content ?? '{}'); $client->getNpubLongForm($npub); diff --git a/src/Controller/SearchController.php b/src/Controller/SearchController.php new file mode 100644 index 0000000..d5edc3a --- /dev/null +++ b/src/Controller/SearchController.php @@ -0,0 +1,18 @@ +render('pages/search.html.twig'); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 3629376..1f24b7d 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -5,13 +5,14 @@ namespace App\Entity; use App\Repository\UserEntityRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Security\Core\User\UserInterface; /** * Entity storing local user representations */ #[ORM\Entity(repositoryClass: UserEntityRepository::class)] #[ORM\Table(name: "app_user")] -class User +class User implements UserInterface { #[ORM\Id] #[ORM\GeneratedValue] @@ -24,6 +25,9 @@ class User #[ORM\Column(type: Types::JSON, nullable: true)] private array $roles = []; + private $metadata = null; + private $relays = null; + public function getRoles(): array { $roles = $this->roles; @@ -67,4 +71,45 @@ class User { $this->npub = $npub; } + + public function eraseCredentials(): void + { + $this->metadata = null; + $this->relays = null; + } + + public function getUserIdentifier(): string + { + return $this->getNpub(); + } + + public function setMetadata($metadata) + { + $this->metadata = $metadata; + } + + public function getMetadata() + { + return $this->metadata; + } + + public function getDisplayName() { + return $this->metadata->name; + } + + /** + * @param mixed $relays + */ + public function setRelays($relays): void + { + $this->relays = $relays; + } + + /** + * @return null|array + */ + public function getRelays(): ?array + { + return $this->relays; + } } diff --git a/src/Security/NostrAuthenticator.php b/src/Security/NostrAuthenticator.php index f4c28a3..b67b797 100644 --- a/src/Security/NostrAuthenticator.php +++ b/src/Security/NostrAuthenticator.php @@ -44,10 +44,10 @@ class NostrAuthenticator extends AbstractAuthenticator implements InteractiveAut if (time() > $event->getCreatedAt() + 60) { throw new AuthenticationException('Expired'); } - $validity = (new SchnorrSignature())->verify($event->getPubkey(), $event->getSig(), $event->getId()); - if (!$validity) { - throw new AuthenticationException('Invalid Authorization header'); - } + // $validity = (new SchnorrSignature())->verify($event->getPubkey(), $event->getSig(), $event->getId()); +// if (!$validity) { +// throw new AuthenticationException('Invalid Authorization header'); +// } return new SelfValidatingPassport( new UserBadge($event->getPubkey()) diff --git a/src/Security/UserDTO.php b/src/Security/UserDTO.php deleted file mode 100644 index 88ab301..0000000 --- a/src/Security/UserDTO.php +++ /dev/null @@ -1,63 +0,0 @@ -user = $user; - $this->metadata = $metadata; - $this->relays = $relays; - } - - public function getUser(): User - { - return $this->user; - } - - public function getMetadata() - { - return $this->metadata; - } - - public function getDisplayName() { - return $this->metadata->name; - } - - /** - * @return null|array - */ - public function getRelays(): ?array - { - return $this->relays; - } - - // Delegate UserInterface methods to the wrapped User entity - public function getRoles(): array - { - return $this->user->getRoles(); - } - - public function eraseCredentials(): void - { - $this->metadata = null; - $this->relays = null; - } - - public function getUserIdentifier(): string - { - return $this->user->getNpub(); - } - - public function getNpub(): string { - return $this->user->getNpub(); - } -} diff --git a/src/Security/UserDTOProvider.php b/src/Security/UserDTOProvider.php index 2fff607..7f5d625 100644 --- a/src/Security/UserDTOProvider.php +++ b/src/Security/UserDTOProvider.php @@ -20,7 +20,7 @@ readonly class UserDTOProvider implements UserProviderInterface */ public function refreshUser(UserInterface $user): UserInterface { - if (!$user instanceof UserDTO) { + if (!$user instanceof User) { throw new \InvalidArgumentException('Invalid user type.'); } @@ -32,7 +32,7 @@ readonly class UserDTOProvider implements UserProviderInterface */ public function supportsClass(string $class): bool { - return $class === UserDTO::class; + return $class === User::class; } /** @@ -41,6 +41,7 @@ readonly class UserDTOProvider implements UserProviderInterface public function loadUserByIdentifier(string $identifier): UserInterface { $user = $this->entityManager->getRepository(User::class)->findOneBy(['npub' => $identifier]); + $metadata = $relays = null; if (!$user) { // user @@ -67,7 +68,10 @@ readonly class UserDTOProvider implements UserProviderInterface $metadata->name = substr($identifier, 0, 5) . ':' . substr($identifier, -5); } - return new UserDTO($user, $metadata ?? null, $relays ?? null); + $user->setMetadata($metadata); + $user->setRelays($relays); + + return $user; } } diff --git a/src/Twig/Components/SearchComponent.php b/src/Twig/Components/SearchComponent.php new file mode 100644 index 0000000..f4cf1cb --- /dev/null +++ b/src/Twig/Components/SearchComponent.php @@ -0,0 +1,34 @@ +finder = $finder; + } + + public function getResults() + { + if (empty($this->query)) { + return []; + } + $res = $this->finder->find($this->query, 10); // Limit to 10 results + return $res; // Limit to 10 results + } + +} diff --git a/templates/components/Atoms/NameOrNpub.html.twig b/templates/components/Atoms/NameOrNpub.html.twig index 58f3944..c6a81f6 100644 --- a/templates/components/Atoms/NameOrNpub.html.twig +++ b/templates/components/Atoms/NameOrNpub.html.twig @@ -1,7 +1,7 @@ {% if author.display_name is defined and author.display_name is not empty %} {{ author.display_name }} - {% elseif author.name is not empty %} + {% elseif author.name is defined and author.name is not empty %} {{ author.name }} {% endif %} diff --git a/templates/components/SearchComponent.html.twig b/templates/components/SearchComponent.html.twig new file mode 100644 index 0000000..bd389c6 --- /dev/null +++ b/templates/components/SearchComponent.html.twig @@ -0,0 +1,20 @@ +
{{ 'text.noResults'|trans }}
+ {% endif %} +