Merge pull request 'Some improvements & bugfixes' (#18) from dev into master
All checks were successful
Build on push / prepare (push) Successful in 7s
Build on push / build-redirector (push) Successful in 31s
Build on push / build-api (push) Successful in 32s
Build on push / build-web (push) Successful in 58s

Reviewed-on: #18
This commit is contained in:
Sven Heidemann 2025-03-14 21:08:49 +01:00
commit 0b9489f110
11 changed files with 92 additions and 61 deletions

View File

@ -13,10 +13,11 @@ class DbModelFilterABC[T](FilterABC[T]):
obj: Optional[dict], obj: Optional[dict],
): ):
FilterABC.__init__(self, obj) FilterABC.__init__(self, obj)
from api_graphql.filter.user_filter import UserFilter
self.add_field("id", IntFilter) self.add_field("id", IntFilter)
self.add_field("deleted", BoolFilter) self.add_field("deleted", BoolFilter)
self.add_field("editor", IntFilter) self.add_field("editor", UserFilter)
self.add_field("createdUtc", StringFilter, "created") self.add_field("createdUtc", StringFilter, "created")
self.add_field("updatedUtc", StringFilter, "updated") self.add_field("updatedUtc", StringFilter, "updated")

View File

@ -1,5 +1,7 @@
from api_graphql.abc.db_model_filter_abc import DbModelFilterABC from api_graphql.abc.db_model_filter_abc import DbModelFilterABC
from api_graphql.abc.filter.string_filter import StringFilter from api_graphql.abc.filter.string_filter import StringFilter
from api_graphql.filter.domain_filter import DomainFilter
from api_graphql.filter.group_filter import GroupFilter
class ShortUrlFilter(DbModelFilterABC): class ShortUrlFilter(DbModelFilterABC):
@ -12,3 +14,6 @@ class ShortUrlFilter(DbModelFilterABC):
self.add_field("shortUrl", StringFilter, db_name="short_url") self.add_field("shortUrl", StringFilter, db_name="short_url")
self.add_field("targetUrl", StringFilter, db_name="target_url") self.add_field("targetUrl", StringFilter, db_name="target_url")
self.add_field("description", StringFilter) self.add_field("description", StringFilter)
self.add_field("group", GroupFilter)
self.add_field("domain", DomainFilter)

View File

@ -50,6 +50,8 @@ input ShortUrlFilter {
targetUrl: StringFilter targetUrl: StringFilter
description: StringFilter description: StringFilter
loadingScreen: BooleanFilter loadingScreen: BooleanFilter
group: GroupFilter
domain: DomainFilter
fuzzy: ShortUrlFuzzy fuzzy: ShortUrlFuzzy

View File

@ -115,5 +115,5 @@ class ShortUrlMutation(MutationABC):
@staticmethod @staticmethod
async def resolve_track_visit(*_, id: int, agent: str): async def resolve_track_visit(*_, id: int, agent: str):
logger.debug(f"track visit: {id} -- {agent}") logger.debug(f"track visit: {id} -- {agent}")
await shortUrlVisitDao.create(ShortUrlVisit(0, id, agent)) x = await shortUrlVisitDao.create(ShortUrlVisit(0, id, agent))
return True return True

View File

@ -132,11 +132,15 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
async def count(self, filters: AttributeFilters = None) -> int: async def count(self, filters: AttributeFilters = None) -> int:
query = f"SELECT COUNT(*) FROM {self._table_name}" query = f"SELECT COUNT(*) FROM {self._table_name}"
for join in self.__joins:
query += f" {self.__joins[join]}"
if filters is not None and (not isinstance(filters, list) or len(filters) > 0): if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
query += f" WHERE {self._build_conditions(filters)}" query += f" WHERE {self._build_conditions(filters)}"
result = await self._db.select_map(query) result = await self._db.select_map(query)
if len(result) == 0:
return 0
return result[0]["count"] return result[0]["count"]
async def get_all(self) -> list[T_DBM]: async def get_all(self) -> list[T_DBM]:
@ -583,14 +587,16 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
if isinstance(sub_values, dict): if isinstance(sub_values, dict):
for operator, value in sub_values.items(): for operator, value in sub_values.items():
conditions.append(self._build_condition(db_name, operator, value)) conditions.append(
f"({self._build_condition(db_name, operator, value)} OR {self._build_condition(db_name, "isNull", None)})")
elif isinstance(sub_values, list): elif isinstance(sub_values, list):
sub_conditions = [] sub_conditions = []
for value in sub_values: for value in sub_values:
if isinstance(value, dict): if isinstance(value, dict):
for operator, val in value.items(): for operator, val in value.items():
sub_conditions.append( sub_conditions.append(
self._build_condition(db_name, operator, val) f"({self._build_condition(db_name, operator, val)} OR {self._build_condition(db_name, "isNull", None)})"
) )
else: else:
sub_conditions.append( sub_conditions.append(

View File

@ -13,7 +13,9 @@ class ShortUrlDao(DbModelDaoABC[ShortUrl]):
self.attribute(ShortUrl.target_url, str) self.attribute(ShortUrl.target_url, str)
self.attribute(ShortUrl.description, str) self.attribute(ShortUrl.description, str)
self.attribute(ShortUrl.group_id, int) self.attribute(ShortUrl.group_id, int)
self.reference("group", "id", ShortUrl.group_id, "public.groups")
self.attribute(ShortUrl.domain_id, int) self.attribute(ShortUrl.domain_id, int)
self.reference("domain", "id", ShortUrl.domain_id, "public.domains")
self.attribute(ShortUrl.loading_screen, bool) self.attribute(ShortUrl.loading_screen, bool)

View File

@ -83,7 +83,7 @@ def _find_short_url_by_path(path: str) -> Optional[dict]:
json={ json={
"query": f""" "query": f"""
query getShortUrlByPath($path: String!) {{ query getShortUrlByPath($path: String!) {{
shortUrls(filter: {{ shortUrl: {{ equal: $path }}, deleted: {{ equal: false }} }}) {{ shortUrls(filter: {{ shortUrl: {{ equal: $path }}, deleted: {{ equal: false }}, group: {{ deleted: {{ equal: false }} }} }}) {{
nodes {{ nodes {{
id id
shortUrl shortUrl
@ -107,7 +107,14 @@ def _find_short_url_by_path(path: str) -> Optional[dict]:
}, },
headers={"Authorization": f"API-Key {api_key}"}, headers={"Authorization": f"API-Key {api_key}"},
) )
data = request.json()["data"]["shortUrls"]["nodes"] data = request.json()
if "errors" in data:
logger.warning(f"Failed to find short url by path {path} -> {data["errors"]}")
if "data" not in data or "shortUrls" not in data["data"] or "nodes" not in data["data"]["shortUrls"]:
return None
data = data["data"]["shortUrls"]["nodes"]
if len(data) == 0: if len(data) == 0:
return None return None

View File

@ -115,6 +115,7 @@ export abstract class PageBase<
.onChange() .onChange()
.pipe(takeUntil(this.unsubscribe$)) .pipe(takeUntil(this.unsubscribe$))
.subscribe(() => { .subscribe(() => {
logger.debug('Reload data');
this.load(true); this.load(true);
}); });
} }

View File

@ -41,12 +41,10 @@ export class ShortUrlFormPageComponent extends FormPageBase<
return; return;
} }
this.dataService this.dataService.loadById(this.nodeId).subscribe(node => {
.load([{ id: { equal: this.nodeId } }]) this.node = node;
.subscribe(apiKey => { this.setForm(this.node);
this.node = apiKey.nodes[0]; });
this.setForm(this.node);
});
} }
new(): ShortUrl { new(): ShortUrl {

View File

@ -1,5 +1,5 @@
import { Injectable, Provider } from '@angular/core'; import { Injectable, Provider } from '@angular/core';
import { Observable } from 'rxjs'; import { merge, Observable } from 'rxjs';
import { import {
Create, Create,
Delete, Delete,
@ -47,13 +47,8 @@ export class ShortUrlsDataService
return this.apollo return this.apollo
.query<{ shortUrls: QueryResult<ShortUrl> }>({ .query<{ shortUrls: QueryResult<ShortUrl> }>({
query: gql` query: gql`
query getShortUrls( query getShortUrls($filter: [ShortUrlFilter], $sort: [ShortUrlSort]) {
$filter: [ShortUrlFilter] shortUrls(filter: $filter, sort: $sort) {
$sort: [ShortUrlSort]
$skip: Int
$take: Int
) {
shortUrls(filter: $filter, sort: $sort, skip: $skip, take: $take) {
count count
totalCount totalCount
nodes { nodes {
@ -80,7 +75,7 @@ export class ShortUrlsDataService
${DB_MODEL_FRAGMENT} ${DB_MODEL_FRAGMENT}
`, `,
variables: { variables: {
filter: filter, filter: [{ group: { deleted: { equal: false } } }, ...(filter ?? [])],
sort: sort, sort: sort,
skip: skip, skip: skip,
take: take, take: take,
@ -100,23 +95,25 @@ export class ShortUrlsDataService
.query<{ shortUrls: QueryResult<ShortUrl> }>({ .query<{ shortUrls: QueryResult<ShortUrl> }>({
query: gql` query: gql`
query getShortUrl($id: Int) { query getShortUrl($id: Int) {
shortUrl(filter: { id: { equal: $id } }) { shortUrls(filter: { id: { equal: $id } }) {
id nodes {
shortUrl
targetUrl
description
loadingScreen
visits
group {
id id
name shortUrl
} targetUrl
domain { description
id loadingScreen
name visits
} group {
id
name
}
domain {
id
name
}
...DB_MODEL ...DB_MODEL
}
} }
} }
@ -136,15 +133,27 @@ export class ShortUrlsDataService
} }
onChange(): Observable<void> { onChange(): Observable<void> {
return this.apollo return merge(
.subscribe<{ shortUrlChange: void }>({ this.apollo
query: gql` .subscribe<{ shortUrlChange: void }>({
subscription onShortUrlChange { query: gql`
shortUrlChange subscription onShortUrlChange {
} shortUrlChange
`, }
}) `,
.pipe(map(result => result.data?.shortUrlChange)); })
.pipe(map(result => result.data?.shortUrlChange)),
this.apollo
.subscribe<{ groupChange: void }>({
query: gql`
subscription onGroupChange {
groupChange
}
`,
})
.pipe(map(result => result.data?.groupChange))
).pipe(map(() => {}));
} }
create(object: ShortUrlCreateInput): Observable<ShortUrl | undefined> { create(object: ShortUrlCreateInput): Observable<ShortUrl | undefined> {

View File

@ -1,34 +1,34 @@
import { NgModule } from "@angular/core"; import { NgModule } from '@angular/core';
import { CommonModule } from "@angular/common"; import { CommonModule } from '@angular/common';
import { SharedModule } from "src/app/modules/shared/shared.module"; import { SharedModule } from 'src/app/modules/shared/shared.module';
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from '@angular/router';
import { PermissionGuard } from "src/app/core/guard/permission.guard"; import { PermissionGuard } from 'src/app/core/guard/permission.guard';
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum"; import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
import { ShortUrlsPage } from "src/app/modules/admin/short-urls/short-urls.page"; import { ShortUrlsPage } from 'src/app/modules/admin/short-urls/short-urls.page';
import { ShortUrlFormPageComponent } from "src/app/modules/admin/short-urls/form-page/short-url-form-page.component"; import { ShortUrlFormPageComponent } from 'src/app/modules/admin/short-urls/form-page/short-url-form-page.component';
import { ShortUrlsDataService } from "src/app/modules/admin/short-urls/short-urls.data.service"; import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
import { ShortUrlsColumns } from "src/app/modules/admin/short-urls/short-urls.columns"; import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns';
const routes: Routes = [ const routes: Routes = [
{ {
path: "", path: '',
title: "ShortUrls", title: 'ShortUrls',
component: ShortUrlsPage, component: ShortUrlsPage,
children: [ children: [
{ {
path: "create", path: 'create',
component: ShortUrlFormPageComponent, component: ShortUrlFormPageComponent,
canActivate: [PermissionGuard], canActivate: [PermissionGuard],
data: { data: {
permissions: [PermissionsEnum.apiKeysCreate], permissions: [PermissionsEnum.shortUrlsCreate],
}, },
}, },
{ {
path: "edit/:id", path: 'edit/:id',
component: ShortUrlFormPageComponent, component: ShortUrlFormPageComponent,
canActivate: [PermissionGuard], canActivate: [PermissionGuard],
data: { data: {
permissions: [PermissionsEnum.apiKeysUpdate], permissions: [PermissionsEnum.shortUrlsUpdate],
}, },
}, },
], ],