From a913ea014fc0ba4e707ce37462d221162de07521 Mon Sep 17 00:00:00 2001 From: edraft Date: Fri, 2 May 2025 11:29:54 +0200 Subject: [PATCH] Fixed redirector #15 --- api/requirements.txt | 1 + api/src/core/database/postgres_pool.py | 5 +- .../schemas/public/short_url_visit_dao.py | 4 +- api/src/redirector.py | 33 ++- .../short-urls/short-urls.data.service.ts | 190 +++++++++--------- 5 files changed, 132 insertions(+), 101 deletions(-) diff --git a/api/requirements.txt b/api/requirements.txt index 88df780..51ce70d 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -11,3 +11,4 @@ Jinja2==3.1.5 python-keycloak==5.3.1 python-multipart==0.0.20 websockets==15.0 +sqlparse==0.5.3 diff --git a/api/src/core/database/postgres_pool.py b/api/src/core/database/postgres_pool.py index 6115c40..87d8b1c 100644 --- a/api/src/core/database/postgres_pool.py +++ b/api/src/core/database/postgres_pool.py @@ -1,5 +1,6 @@ from typing import Optional, Any +import sqlparse from psycopg import sql from psycopg_pool import AsyncConnectionPool, PoolTimeout @@ -44,7 +45,9 @@ class PostgresPool: @staticmethod async def _exec_sql(cursor: Any, query: str, args=None, multi=True): if multi: - queries = query.split(";") + queries = [ + str(stmt).strip() for stmt in sqlparse.parse(query) if str(stmt).strip() + ] for q in queries: if q.strip() == "": continue diff --git a/api/src/data/schemas/public/short_url_visit_dao.py b/api/src/data/schemas/public/short_url_visit_dao.py index 99f72a5..959c25a 100644 --- a/api/src/data/schemas/public/short_url_visit_dao.py +++ b/api/src/data/schemas/public/short_url_visit_dao.py @@ -15,7 +15,9 @@ class ShortUrlVisitDao(DbModelDaoABC[ShortUrlVisit]): async def count_by_id(self, fid: int) -> int: result = await self._db.select_map( - f"SELECT COUNT(*) FROM {self._table_name} WHERE shortUrlId = {fid}" + f"""SELECT COUNT(*) FROM {self._table_name} + WHERE shortUrlId = {fid} + AND deleted = FALSE""" ) if result is None or len(result) == 0: return 0 diff --git a/api/src/redirector.py b/api/src/redirector.py index 28ff9b8..a47cee5 100644 --- a/api/src/redirector.py +++ b/api/src/redirector.py @@ -83,7 +83,26 @@ def _find_short_url_by_path(path: str) -> Optional[dict]: json={ "query": f""" query getShortUrlByPath($path: String!) {{ - shortUrls(filter: {{ shortUrl: {{ equal: $path }}, deleted: {{ equal: false }}, group: {{ deleted: {{ equal: false }} }} }}) {{ + shortUrls(filter: [{{ shortUrl: {{ equal: $path }} }}, {{ deleted: {{ equal: false }} }}, {{ group: {{ deleted: {{ equal: false }} }} }}]) {{ + nodes {{ + id + shortUrl + targetUrl + description + group {{ + id + name + }} + domain {{ + id + name + }} + loadingScreen + deleted + }} + }} + + shortUrlsWithoutGroup: shortUrls(filter: [{{ shortUrl: {{ equal: $path }} }}, {{ deleted: {{ equal: false }} }}, {{ group: {{ isNull: true }} }}]) {{ nodes {{ id shortUrl @@ -115,14 +134,18 @@ def _find_short_url_by_path(path: str) -> Optional[dict]: "data" not in data or "shortUrls" not in data["data"] or "nodes" not in data["data"]["shortUrls"] + or "nodes" not in data["data"]["shortUrlsWithoutGroup"] ): return None - data = data["data"]["shortUrls"]["nodes"] - if len(data) == 0: + nodes = [ + *data["data"]["shortUrls"]["nodes"], + *data["data"]["shortUrlsWithoutGroup"]["nodes"], + ] + if len(nodes) == 0: return None - return data[0] + return nodes[0] async def _handle_short_url(request: Request, short_url: dict): @@ -145,7 +168,7 @@ async def _track_visit(r: Request, short_url: dict): f"{api_url}/graphql", json={ "query": f""" - mutation trackShortUrlVisit($id: ID!, $agent: String) {{ + mutation trackShortUrlVisit($id: Int!, $agent: String) {{ shortUrl {{ trackVisit(id: $id, agent: $agent) }} diff --git a/web/src/app/modules/admin/short-urls/short-urls.data.service.ts b/web/src/app/modules/admin/short-urls/short-urls.data.service.ts index 7b9abdf..8f97da9 100644 --- a/web/src/app/modules/admin/short-urls/short-urls.data.service.ts +++ b/web/src/app/modules/admin/short-urls/short-urls.data.service.ts @@ -50,111 +50,113 @@ export class ShortUrlsDataService skip?: number | undefined, take?: number | undefined ): Observable> { - const query1 = this.apollo.query<{ shortUrls: QueryResult }>({ - query: gql` - query getShortUrls($filter: [ShortUrlFilter], $sort: [ShortUrlSort]) { - shortUrls(filter: $filter, sort: $sort) { - nodes { - id - shortUrl - targetUrl - description - loadingScreen - visits - group { + return this.apollo + .query<{ + shortUrls: QueryResult; + shortUrlsWithoutGroup: QueryResult; + }>({ + query: gql` + query getShortUrls( + $filter: [ShortUrlFilter] + $filter2: [ShortUrlFilter] + $sort: [ShortUrlSort] + ) { + shortUrls(filter: $filter, sort: $sort) { + nodes { id - name - } - domain { - id - name - } + shortUrl + targetUrl + description + loadingScreen + visits + group { + id + name + } + domain { + id + name + } - ...DB_MODEL + ...DB_MODEL + } + } + + shortUrlsWithoutGroup: shortUrls(filter: $filter2, sort: $sort) { + nodes { + id + shortUrl + targetUrl + description + loadingScreen + visits + group { + id + name + } + domain { + id + name + } + + ...DB_MODEL + } } } - } - ${DB_MODEL_FRAGMENT} - `, - variables: { - filter: [ - { - userSpace: { - id: { equal: this.sidebarService.selectedUserSpace$.value?.id }, - }, - }, - { group: { deleted: { equal: false } } }, - { - group: { + ${DB_MODEL_FRAGMENT} + `, + variables: { + filter: [ + { userSpace: { id: { equal: this.sidebarService.selectedUserSpace$.value?.id }, }, }, - }, - ...(filter ?? []), - ], - sort: [{ id: SortOrder.DESC }, ...(sort ?? [])], - skip, - take, - }, - }); - - const query2 = this.apollo.query<{ shortUrls: QueryResult }>({ - query: gql` - query getShortUrls($filter: [ShortUrlFilter], $sort: [ShortUrlSort]) { - shortUrls(filter: $filter, sort: $sort) { - nodes { - id - shortUrl - targetUrl - description - loadingScreen - visits - group { - id - name - } - domain { - id - name - } - - ...DB_MODEL - } - } - } - - ${DB_MODEL_FRAGMENT} - `, - variables: { - filter: [ - { - userSpace: { - id: { equal: this.sidebarService.selectedUserSpace$.value?.id }, + { group: { deleted: { equal: false } } }, + { + group: { + userSpace: { + id: { + equal: this.sidebarService.selectedUserSpace$.value?.id, + }, + }, + }, }, - }, - { group: { isNull: true } }, - ...(filter ?? []), - ], - sort: [{ id: SortOrder.DESC }, ...(sort ?? [])], - skip, - take, - }, - }); - - return forkJoin([query1, query2]).pipe( - map(([result1, result2]) => { - const nodes = [ - ...result1.data.shortUrls.nodes, - ...result2.data.shortUrls.nodes, - ]; - const uniqueNodes = Array.from( - new Map(nodes.map(node => [node.id, node])).values() - ); - return { ...result1.data.shortUrls, nodes: uniqueNodes }; + ...(filter ?? []), + ], + filter2: [ + { + userSpace: { + id: { equal: this.sidebarService.selectedUserSpace$.value?.id }, + }, + }, + { + group: { + isNull: true, + }, + }, + ...(filter ?? []), + ], + sort: [{ id: SortOrder.DESC }, ...(sort ?? [])], + skip, + take, + }, }) - ); + .pipe( + map(x => { + return { + count: x.data.shortUrls.count + x.data.shortUrlsWithoutGroup.count, + totalCount: + x.data.shortUrls.totalCount + + x.data.shortUrlsWithoutGroup.totalCount, + nodes: [ + ...x.data.shortUrls.nodes, + ...x.data.shortUrlsWithoutGroup.nodes, + ], + }; + }) + ); } loadById(id: number): Observable {