diff --git a/assets/App.tsx b/assets/App.tsx index cead534..2bd4a37 100644 --- a/assets/App.tsx +++ b/assets/App.tsx @@ -48,7 +48,8 @@ export default function App() { if (location.pathname === '/login') navigate('/home') }).catch(() => { setIsAuthenticated(false) - if (location.pathname !== '/login') navigate('/home') + const pathname = location.pathname + if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) navigate('/home') }) }, []); diff --git a/assets/components/RegisterForm.tsx b/assets/components/RegisterForm.tsx index 9f15f1a..b19391e 100644 --- a/assets/components/RegisterForm.tsx +++ b/assets/components/RegisterForm.tsx @@ -19,6 +19,13 @@ export function RegisterForm() { register(data.username, data.password).then(() => { navigate('/home') }).catch((e) => { + + if (e.response?.status === 429) { + const duration = e.response.headers['retry-after'] + setError(t`Please retry after ${duration} seconds`) + return; + } + if (e.response.data.message !== undefined) { setError(e.response.data.message) } else { diff --git a/assets/pages/search/DomainSearchPage.tsx b/assets/pages/search/DomainSearchPage.tsx index 451af9d..60723bc 100644 --- a/assets/pages/search/DomainSearchPage.tsx +++ b/assets/pages/search/DomainSearchPage.tsx @@ -19,9 +19,15 @@ export default function DomainSearchPage() { setDomain(d) messageApi.success(t`Found !`) }).catch((e: AxiosError) => { - const data = e?.response?.data as { detail: string } setDomain(undefined) - messageApi.error(data.detail ?? t`An error occurred`) + + if (e.response?.status === 429) { + const duration = e.response.headers['retry-after'] + messageApi.error(t`Please retry after ${duration} seconds`) + return; + } + const data = e?.response?.data as { detail: string } + messageApi.error(data.detail !== '' ? data.detail : t`An error occurred`) }) } diff --git a/migrations/Version20240806170123.php b/migrations/Version20240806170123.php new file mode 100644 index 0000000..1b56581 --- /dev/null +++ b/migrations/Version20240806170123.php @@ -0,0 +1,34 @@ +addSql('ALTER TABLE domain_entity ADD updated_at DATE DEFAULT CURRENT_TIMESTAMP NOT NULL'); + $this->addSql('COMMENT ON COLUMN domain_entity.updated_at IS \'(DC2Type:date_immutable)\''); + $this->addSql('ALTER TABLE domain_entity ALTER updated_at DROP DEFAULT'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE domain_entity DROP updated_at'); + } +} diff --git a/src/Config/Connector/OvhConnector.php b/src/Config/Connector/OvhConnector.php index 810defa..936dc3c 100644 --- a/src/Config/Connector/OvhConnector.php +++ b/src/Config/Connector/OvhConnector.php @@ -4,6 +4,7 @@ namespace App\Config\Connector; use App\Entity\Domain; use Ovh\Api; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\HttpException; readonly class OvhConnector implements ConnectorInterface @@ -138,7 +139,7 @@ readonly class OvhConnector implements ConnectorInterface || !is_string($ovhSubsidiary) || empty($ovhSubsidiary) || !is_string($pricingMode) || empty($pricingMode) ) { - throw new \Exception('Bad authData schema'); + throw new BadRequestHttpException('Bad authData schema'); } if (true !== $acceptConditions @@ -177,7 +178,7 @@ readonly class OvhConnector implements ConnectorInterface } if (!$ok) { - throw new \Exception('The credentials provided do not have enough permissions to purchase a domain name.'); + throw new BadRequestHttpException('The credentials provided do not have enough permissions to purchase a domain name.'); } } diff --git a/src/Controller/DomainRefreshController.php b/src/Controller/DomainRefreshController.php index b24344a..e45bc36 100644 --- a/src/Controller/DomainRefreshController.php +++ b/src/Controller/DomainRefreshController.php @@ -31,10 +31,10 @@ class DomainRefreshController extends AbstractController /** * @throws TransportExceptionInterface - * @throws HttpExceptionInterface * @throws DecodingExceptionInterface * @throws ExceptionInterface * @throws \Exception + * @throws HttpExceptionInterface */ public function __invoke(string $ldhName, KernelInterface $kernel): ?Domain { diff --git a/src/Entity/DomainEntity.php b/src/Entity/DomainEntity.php index 50ad16d..54a8f00 100644 --- a/src/Entity/DomainEntity.php +++ b/src/Entity/DomainEntity.php @@ -27,6 +27,15 @@ class DomainEntity #[Groups(['domain-entity:entity', 'domain-entity:domain'])] private array $roles = []; + #[ORM\Column(type: Types::DATE_IMMUTABLE)] + #[Groups(['domain-entity:entity', 'domain-entity:domain'])] + private ?\DateTimeImmutable $updatedAt = null; + + public function __construct() + { + $this->updatedAt = new \DateTimeImmutable('now'); + } + public function getDomain(): ?Domain { return $this->domain; @@ -65,4 +74,23 @@ class DomainEntity return $this; } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(\DateTimeImmutable $updatedAt): static + { + $this->updatedAt = $updatedAt; + + return $this; + } + + #[ORM\PrePersist] + #[ORM\PreUpdate] + public function updateTimestamps(): void + { + $this->setUpdatedAt(new \DateTimeImmutable('now')); + } } diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index 92e7d5e..4de1203 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -25,6 +25,8 @@ use App\Repository\TldRepository; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Exception\ORMException; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; @@ -122,10 +124,10 @@ readonly class RDAPService } /** - * @throws \Exception * @throws TransportExceptionInterface * @throws DecodingExceptionInterface * @throws HttpExceptionInterface + * @throws \Exception */ public function registerDomain(string $fqdn): Domain { @@ -141,7 +143,7 @@ readonly class RDAPService $rdapServer = $this->rdapServerRepository->findOneBy(['tld' => $tld], ['updatedAt' => 'DESC']); if (null === $rdapServer) { - throw new \Exception('Unable to determine which RDAP server to contact'); + throw new NotFoundHttpException('Unable to determine which RDAP server to contact'); } /** @var ?Domain $domain */ @@ -258,7 +260,8 @@ readonly class RDAPService $domain->addDomainEntity($domainEntity ->setDomain($domain) ->setEntity($entity) - ->setRoles($roles)); + ->setRoles($roles)) + ->updateTimestamps(); $this->em->persist($domainEntity); $this->em->flush(); @@ -330,14 +333,11 @@ readonly class RDAPService return $domain; } - /** - * @throws \Exception - */ private function getTld($domain): ?object { $lastDotPosition = strrpos($domain, '.'); if (false === $lastDotPosition) { - throw new \Exception('Domain must contain at least one dot'); + throw new BadRequestException('Domain must contain at least one dot'); } $tld = strtolower(substr($domain, $lastDotPosition + 1)); diff --git a/translations/fr.po b/translations/fr.po index 59301f3..88fbe05 100644 --- a/translations/fr.po +++ b/translations/fr.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2024-08-05 01:44+0000\n" +"PO-Revision-Date: 2024-08-06 15:41+0000\n" "Last-Translator: Maël Gangloff \n" "Language-Team: French \n" @@ -11,16 +11,16 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 5.6\n" -#: assets/components/LoginForm.tsx:47 assets/components/RegisterForm.tsx:33 +#: assets/components/LoginForm.tsx:47 assets/components/RegisterForm.tsx:40 msgid "Error" msgstr "Erreur" -#: assets/components/LoginForm.tsx:62 assets/components/RegisterForm.tsx:48 +#: assets/components/LoginForm.tsx:62 assets/components/RegisterForm.tsx:55 msgid "Email address" msgstr "Adresse e-mail" #: assets/components/LoginForm.tsx:64 assets/components/LoginForm.tsx:72 -#: assets/components/RegisterForm.tsx:50 assets/components/RegisterForm.tsx:58 +#: assets/components/RegisterForm.tsx:57 assets/components/RegisterForm.tsx:65 #: assets/components/search/DomainSearchBar.tsx:23 #: assets/components/tracking/ConnectorForm.tsx:40 #: assets/components/tracking/ConnectorForm.tsx:66 @@ -34,7 +34,7 @@ msgstr "Adresse e-mail" msgid "Required" msgstr "Requis" -#: assets/components/LoginForm.tsx:70 assets/components/RegisterForm.tsx:56 +#: assets/components/LoginForm.tsx:70 assets/components/RegisterForm.tsx:63 msgid "Password" msgstr "Mot de passe" @@ -366,7 +366,13 @@ msgstr "Se déconnecter" msgid "Log in" msgstr "Se connecter" -#: assets/components/RegisterForm.tsx:65 assets/pages/LoginPage.tsx:30 +#: assets/components/RegisterForm.tsx:25 +#: assets/pages/search/DomainSearchPage.tsx:26 +#, javascript-format +msgid "Please retry after ${ duration } seconds" +msgstr "Merci de réessayer dans ${ duration } secondes" + +#: assets/components/RegisterForm.tsx:72 assets/pages/LoginPage.tsx:30 msgid "Register" msgstr "S'enregistrer" @@ -374,7 +380,7 @@ msgstr "S'enregistrer" msgid "Found !" msgstr "Trouvé !" -#: assets/pages/search/DomainSearchPage.tsx:24 +#: assets/pages/search/DomainSearchPage.tsx:30 #: assets/pages/tracking/ConnectorsPage.tsx:21 #: assets/pages/tracking/ConnectorsPage.tsx:29 #: assets/pages/tracking/WatchlistPage.tsx:49 @@ -383,23 +389,23 @@ msgstr "Trouvé !" msgid "An error occurred" msgstr "Une erreur s'est produite" -#: assets/pages/search/DomainSearchPage.tsx:29 +#: assets/pages/search/DomainSearchPage.tsx:35 msgid "Domain finder" msgstr "Rechercher un domaine" -#: assets/pages/search/DomainSearchPage.tsx:50 +#: assets/pages/search/DomainSearchPage.tsx:56 msgid "EPP Status Codes" msgstr "Codes de statut EPP" -#: assets/pages/search/DomainSearchPage.tsx:60 +#: assets/pages/search/DomainSearchPage.tsx:66 msgid "Timeline" msgstr "Chronologie" -#: assets/pages/search/DomainSearchPage.tsx:65 +#: assets/pages/search/DomainSearchPage.tsx:71 msgid "Entities" msgstr "Entités" -#: assets/pages/search/DomainSearchPage.tsx:73 +#: assets/pages/search/DomainSearchPage.tsx:79 msgid "" "Although the domain exists in my database, it has been deleted from the " "WHOIS by its registrar." @@ -566,23 +572,23 @@ msgstr "" "Le domaine est libre mais est un premium. Son prix est variable d'un domaine " "à l'autre" -#: assets/App.tsx:100 +#: assets/App.tsx:101 msgid "TOS" msgstr "CGU" -#: assets/App.tsx:101 +#: assets/App.tsx:102 msgid "Privacy Policy" msgstr "Politique de confidentialité" -#: assets/App.tsx:102 +#: assets/App.tsx:103 msgid "FAQ" msgstr "FAQ" -#: assets/App.tsx:104 +#: assets/App.tsx:105 msgid "Documentation" msgstr "Documentation" -#: assets/App.tsx:107 +#: assets/App.tsx:108 #, javascript-format msgid "" "${ ProjectLink } is an open source project distributed under the " diff --git a/translations/translations.pot b/translations/translations.pot index a87363a..7087fe3 100644 --- a/translations/translations.pot +++ b/translations/translations.pot @@ -4,19 +4,19 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: assets/components/LoginForm.tsx:47 -#: assets/components/RegisterForm.tsx:33 +#: assets/components/RegisterForm.tsx:40 msgid "Error" msgstr "" #: assets/components/LoginForm.tsx:62 -#: assets/components/RegisterForm.tsx:48 +#: assets/components/RegisterForm.tsx:55 msgid "Email address" msgstr "" #: assets/components/LoginForm.tsx:64 #: assets/components/LoginForm.tsx:72 -#: assets/components/RegisterForm.tsx:50 -#: assets/components/RegisterForm.tsx:58 +#: assets/components/RegisterForm.tsx:57 +#: assets/components/RegisterForm.tsx:65 #: assets/components/search/DomainSearchBar.tsx:23 #: assets/components/tracking/ConnectorForm.tsx:40 #: assets/components/tracking/ConnectorForm.tsx:66 @@ -32,7 +32,7 @@ msgid "Required" msgstr "" #: assets/components/LoginForm.tsx:70 -#: assets/components/RegisterForm.tsx:56 +#: assets/components/RegisterForm.tsx:63 msgid "Password" msgstr "" @@ -369,7 +369,13 @@ msgstr "" msgid "Log in" msgstr "" -#: assets/components/RegisterForm.tsx:65 +#: assets/components/RegisterForm.tsx:25 +#: assets/pages/search/DomainSearchPage.tsx:26 +#, javascript-format +msgid "Please retry after ${ duration } seconds" +msgstr "" + +#: assets/components/RegisterForm.tsx:72 #: assets/pages/LoginPage.tsx:30 msgid "Register" msgstr "" @@ -378,7 +384,7 @@ msgstr "" msgid "Found !" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:24 +#: assets/pages/search/DomainSearchPage.tsx:30 #: assets/pages/tracking/ConnectorsPage.tsx:21 #: assets/pages/tracking/ConnectorsPage.tsx:29 #: assets/pages/tracking/WatchlistPage.tsx:49 @@ -387,23 +393,23 @@ msgstr "" msgid "An error occurred" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:29 +#: assets/pages/search/DomainSearchPage.tsx:35 msgid "Domain finder" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:50 +#: assets/pages/search/DomainSearchPage.tsx:56 msgid "EPP Status Codes" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:60 +#: assets/pages/search/DomainSearchPage.tsx:66 msgid "Timeline" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:65 +#: assets/pages/search/DomainSearchPage.tsx:71 msgid "Entities" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:73 +#: assets/pages/search/DomainSearchPage.tsx:79 msgid "" "Although the domain exists in my database, it has been deleted from the " "WHOIS by its registrar." @@ -552,23 +558,23 @@ msgid "" "another" msgstr "" -#: assets/App.tsx:100 +#: assets/App.tsx:101 msgid "TOS" msgstr "" -#: assets/App.tsx:101 +#: assets/App.tsx:102 msgid "Privacy Policy" msgstr "" -#: assets/App.tsx:102 +#: assets/App.tsx:103 msgid "FAQ" msgstr "" -#: assets/App.tsx:104 +#: assets/App.tsx:105 msgid "Documentation" msgstr "" -#: assets/App.tsx:107 +#: assets/App.tsx:108 #, javascript-format msgid "" "${ ProjectLink } is an open source project distributed under the ${ "