From 5ffd66d06d0c1c3ee584d435b5e0dd9a4e3e7146 Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 2 Jan 2025 15:56:53 +0100 Subject: [PATCH] Handle short urls with loading screen Closes #1 Closes #3 --- api/src/api/routes/redirect.py | 13 +++++++++---- api/src/api_graphql/graphql/short_url.gql | 5 +++++ .../api_graphql/input/short_url_create_input.py | 5 +++++ .../api_graphql/input/short_url_update_input.py | 5 +++++ .../api_graphql/mutations/short_url_mutation.py | 4 ++++ api/src/api_graphql/queries/short_url_query.py | 1 + api/src/data/schemas/public/short_url.py | 11 +++++++++++ api/src/data/schemas/public/short_url_dao.py | 1 + .../scripts/2025-01-02-14-15-loading-screen.sql | 6 ++++++ web/src/app/app.component.html | 4 ++-- web/src/app/app.component.ts | 1 + web/src/app/components/home/home.component.ts | 10 ++++------ .../components/redirect/redirect.component.html | 17 ++++++++++++++++- .../components/redirect/redirect.component.scss | 5 +++++ .../components/redirect/redirect.component.ts | 17 ++++++++++++++++- web/src/app/model/entities/short-url.ts | 1 + 16 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 api/src/data/scripts/2025-01-02-14-15-loading-screen.sql diff --git a/api/src/api/routes/redirect.py b/api/src/api/routes/redirect.py index c6932a3..79a232c 100644 --- a/api/src/api/routes/redirect.py +++ b/api/src/api/routes/redirect.py @@ -1,6 +1,4 @@ -import json - -from flask import redirect, jsonify, request +from flask import redirect, jsonify, request, Response from api.route import Route from core.logger import Logger @@ -41,4 +39,11 @@ async def handle_short_url(path: str): except Exception as e: logger.error(f"Failed to update short url {from_db.short_url} with error", e) - return redirect(from_db.target_url) + return _do_redirect(from_db.target_url) + +def _do_redirect(url: str) -> Response: + # todo: multiple protocols like ts3:// + if not url.startswith("http://") and not url.startswith("https://"): + url = f"http://{url}" + + return redirect(url) diff --git a/api/src/api_graphql/graphql/short_url.gql b/api/src/api_graphql/graphql/short_url.gql index 90fee2d..ce8f9d8 100644 --- a/api/src/api_graphql/graphql/short_url.gql +++ b/api/src/api_graphql/graphql/short_url.gql @@ -11,6 +11,7 @@ type ShortUrl implements DbModel { description: String visits: Int group: Group + loadingScreen: Boolean deleted: Boolean editor: User @@ -22,6 +23,7 @@ input ShortUrlSort { id: SortOrder name: SortOrder description: SortOrder + loadingScreen: SortOrder deleted: SortOrder editorId: SortOrder @@ -33,6 +35,7 @@ input ShortUrlFilter { id: IntFilter name: StringFilter description: StringFilter + loadingScreen: BooleanFilter deleted: BooleanFilter editor: IntFilter @@ -52,6 +55,7 @@ input ShortUrlCreateInput { targetUrl: String! description: String groupId: ID + loadingScreen: Boolean } input ShortUrlUpdateInput { @@ -60,4 +64,5 @@ input ShortUrlUpdateInput { targetUrl: String description: String groupId: ID + loadingScreen: Boolean } diff --git a/api/src/api_graphql/input/short_url_create_input.py b/api/src/api_graphql/input/short_url_create_input.py index c60c38c..c5f0fea 100644 --- a/api/src/api_graphql/input/short_url_create_input.py +++ b/api/src/api_graphql/input/short_url_create_input.py @@ -12,6 +12,7 @@ class ShortUrlCreateInput(InputABC): self._target_url = self.option("targetUrl", str, required=True) self._description = self.option("description", str) self._group_id = self.option("groupId", int) + self._loading_screen = self.option("loadingScreen", str) @property def short_url(self) -> str: @@ -28,3 +29,7 @@ class ShortUrlCreateInput(InputABC): @property def group_id(self) -> Optional[int]: return self._group_id + + @property + def loading_screen(self) -> Optional[str]: + return self._loading_screen diff --git a/api/src/api_graphql/input/short_url_update_input.py b/api/src/api_graphql/input/short_url_update_input.py index 200cb2d..d9628e7 100644 --- a/api/src/api_graphql/input/short_url_update_input.py +++ b/api/src/api_graphql/input/short_url_update_input.py @@ -13,6 +13,7 @@ class ShortUrlUpdateInput(InputABC): self._target_url = self.option("targetUrl", str) self._description = self.option("description", str) self._group_id = self.option("groupId", int) + self._loading_screen = self.option("loadingScreen", str) @property def id(self) -> int: @@ -33,3 +34,7 @@ class ShortUrlUpdateInput(InputABC): @property def group_id(self) -> Optional[int]: return self._group_id + + @property + def loading_screen(self) -> Optional[str]: + return self._loading_screen diff --git a/api/src/api_graphql/mutations/short_url_mutation.py b/api/src/api_graphql/mutations/short_url_mutation.py index 2977683..cc67d8d 100644 --- a/api/src/api_graphql/mutations/short_url_mutation.py +++ b/api/src/api_graphql/mutations/short_url_mutation.py @@ -49,6 +49,7 @@ class ShortUrlMutation(MutationABC): obj.target_url, obj.description, obj.group_id, + obj.loading_screen, ) nid = await shortUrlDao.create(short_url) return await shortUrlDao.get_by_id(nid) @@ -74,6 +75,9 @@ class ShortUrlMutation(MutationABC): raise NotFound(f"Group with id {obj.group_id} does not exist") short_url.group_id = obj.group_id + if obj.loading_screen is not None: + short_url.loading_screen = obj.loading_screen + await shortUrlDao.update(short_url) return await shortUrlDao.get_by_id(obj.id) diff --git a/api/src/api_graphql/queries/short_url_query.py b/api/src/api_graphql/queries/short_url_query.py index 592f934..29c750a 100644 --- a/api/src/api_graphql/queries/short_url_query.py +++ b/api/src/api_graphql/queries/short_url_query.py @@ -10,3 +10,4 @@ class ShortUrlQuery(DbModelQueryABC): self.set_field("description", lambda x, *_: x.description) self.set_field("group", lambda x, *_: x.group) self.set_field("visits", lambda x, *_: x.visit_count) + self.set_field("loadingScreen", lambda x, *_: x.loading_screen) diff --git a/api/src/data/schemas/public/short_url.py b/api/src/data/schemas/public/short_url.py index ab7cf23..3c699bf 100644 --- a/api/src/data/schemas/public/short_url.py +++ b/api/src/data/schemas/public/short_url.py @@ -16,6 +16,7 @@ class ShortUrl(DbModelABC): target_url: str, description: Optional[str], group_id: Optional[SerialId], + loading_screen: Optional[str] = None, deleted: bool = False, editor_id: Optional[SerialId] = None, created: Optional[datetime] = None, @@ -26,6 +27,7 @@ class ShortUrl(DbModelABC): self._target_url = target_url self._description = description self._group_id = group_id + self._loading_screen = loading_screen @property def short_url(self) -> str: @@ -74,8 +76,17 @@ class ShortUrl(DbModelABC): return await shortUrlVisitDao.count_by_id(self.id) + @property + def loading_screen(self) -> Optional[str]: + return self._loading_screen + + @loading_screen.setter + def loading_screen(self, value: Optional[str]): + self._loading_screen = value + def to_dto(self) -> dict: return { "id": self.id, "target_url": self.target_url, + "loadingScreen": self.loading_screen, } diff --git a/api/src/data/schemas/public/short_url_dao.py b/api/src/data/schemas/public/short_url_dao.py index b7cf1a2..90165ac 100644 --- a/api/src/data/schemas/public/short_url_dao.py +++ b/api/src/data/schemas/public/short_url_dao.py @@ -13,6 +13,7 @@ class ShortUrlDao(DbModelDaoABC[ShortUrl]): self.attribute(ShortUrl.target_url, str) self.attribute(ShortUrl.description, str) self.attribute(ShortUrl.group_id, int) + self.attribute(ShortUrl.loading_screen, bool) shortUrlDao = ShortUrlDao() diff --git a/api/src/data/scripts/2025-01-02-14-15-loading-screen.sql b/api/src/data/scripts/2025-01-02-14-15-loading-screen.sql new file mode 100644 index 0000000..9164634 --- /dev/null +++ b/api/src/data/scripts/2025-01-02-14-15-loading-screen.sql @@ -0,0 +1,6 @@ +ALTER TABLE public.short_urls + ADD COLUMN IF NOT EXISTS loadingScreen boolean DEFAULT true; + +ALTER TABLE public.short_urls_history + ADD COLUMN IF NOT EXISTS loadingScreen boolean NULL; + diff --git a/web/src/app/app.component.html b/web/src/app/app.component.html index 5d586fc..8152918 100644 --- a/web/src/app/app.component.html +++ b/web/src/app/app.component.html @@ -1,8 +1,8 @@ -
+
-