Improved queries & filtering #162

This commit is contained in:
Sven Heidemann 2023-01-16 15:13:45 +01:00
parent c75cc54d16
commit 807827e30f
12 changed files with 207 additions and 84 deletions

@ -1 +1 @@
Subproject commit 54b1b3860cb570d29c8ba2590dd082a1fa744265 Subproject commit b0ae87621bbe54fd9c5650071ec8c1c9ec32df48

View File

@ -3,7 +3,6 @@ from ariadne.constants import PLAYGROUND_HTML
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
from cpl_core.environment import ApplicationEnvironmentABC from cpl_core.environment import ApplicationEnvironmentABC
from flask import request, jsonify from flask import request, jsonify
from graphql import MiddlewareManager
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route from bot_api.route.route import Route

View File

@ -0,0 +1,20 @@
from datetime import datetime
from bot_graphql.abc.query_type_abc import QueryTypeABC
class DiscordQueryTypeABC(QueryTypeABC):
def __init__(
self,
id: str,
discord_id: str,
created_at: datetime = None,
modified_at: datetime = None,
):
QueryTypeABC.__init__(self, id, created_at, modified_at)
self._discord_id = discord_id
@property
def discord_id(self) -> str:
return self._discord_id

View File

@ -1,5 +1,5 @@
import functools import functools
from abc import ABC from abc import ABC, abstractmethod
from inspect import signature, Parameter from inspect import signature, Parameter
from typing import Optional from typing import Optional
@ -40,6 +40,14 @@ class FilterABC(ABC):
return query return query
@abstractmethod
def from_dict(self, values: dict):
pass
@abstractmethod
def filter(self, query: List, *args) -> List:
pass
@staticmethod @staticmethod
@ServiceProviderABC.inject @ServiceProviderABC.inject
def get_filter(f, values: dict, services: ServiceProviderABC): def get_filter(f, values: dict, services: ServiceProviderABC):
@ -52,7 +60,14 @@ class FilterABC(ABC):
if issubclass(parameter.annotation, FilterABC): if issubclass(parameter.annotation, FilterABC):
filter = services.get_service(parameter.annotation) filter = services.get_service(parameter.annotation)
filter.from_dict(values) filter.from_dict(values)
return filter return filter @ staticmethod
@staticmethod
@ServiceProviderABC.inject
def get_collection_filter(filter_type: type, values: dict, services: ServiceProviderABC):
filter: FilterABC = services.get_service(filter_type)
filter.from_dict(values)
return filter
@classmethod @classmethod
def resolve_filter_annotation(cls, f=None): def resolve_filter_annotation(cls, f=None):

View File

@ -1,5 +1,30 @@
from typing import Callable
from ariadne import ObjectType from ariadne import ObjectType
from cpl_query.extension import List
from bot_graphql.abc.filter_abc import FilterABC
class QueryABC(ObjectType): class QueryABC(ObjectType):
__abstract__ = True __abstract__ = True
def __init__(self, name: str):
ObjectType.__init__(self, name)
def _add_collection(self, name: str, get_collection: Callable, filter_type: type = None):
def wrapper(*args, **kwargs):
if filter_type is not None and "filter" in kwargs:
kwargs["filter"] = FilterABC.get_collection_filter(filter_type, kwargs["filter"])
else:
kwargs["filter"] = None
return self._resolve_collection(get_collection(*args), *args, **kwargs)
self.set_field(f"{name}s", wrapper)
self.set_field(f"{name}_count", lambda *args: get_collection(*args).count())
def _resolve_collection(self, collection: List, *_, filter: FilterABC = None):
if filter is not None:
return filter.filter(collection)
return collection

View File

@ -0,0 +1,29 @@
from abc import ABC
from datetime import datetime
from typing import Optional
class QueryTypeABC(ABC):
def __init__(
self,
id: str,
created_at: datetime = None,
modified_at: datetime = None,
):
ABC.__init__(self)
self._id = id
self._created_at = created_at
self._modified_at = modified_at
@property
def id(self) -> str:
return self._id
@property
def created_at(self) -> Optional[datetime]:
return self._created_at
@property
def modified_at(self) -> Optional[datetime]:
return self._modified_at

View File

@ -1,15 +1,25 @@
from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List from cpl_query.extension import List
from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_data.model.user import User from bot_data.model.user import User
from bot_graphql.abc.filter_abc import FilterABC from bot_graphql.abc.filter_abc import FilterABC
from modules.level.service.level_service import LevelService
class UserFilter(FilterABC): class UserFilter(FilterABC):
def __init__( def __init__(
self, self,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
levels: LevelService,
): ):
FilterABC.__init__(self) FilterABC.__init__(self)
self._bot = bot
self._client_utils = client_utils
self._levels = levels
self._id = None self._id = None
self._discord_id = None self._discord_id = None
self._name = None self._name = None
@ -38,10 +48,19 @@ class UserFilter(FilterABC):
if self._discord_id is not None: if self._discord_id is not None:
query = query.where(lambda x: x.discord_id == self._discord_id) query = query.where(lambda x: x.discord_id == self._discord_id)
if self._name is not None:
query = query.where(
lambda x: self._bot.get_user(x.discord_id).name == self._name
or self._name in self._bot.get_user(x.discord_id).name
)
if self._xp is not None: if self._xp is not None:
query = query.where(lambda x: x.xp == self._xp) query = query.where(lambda x: x.xp == self._xp)
# if self._ontime is not None: if self._ontime is not None:
# query = query.where(lambda x: self._client_utils.get_ontime_for_user(x) == self._ontime) query = query.where(lambda x: self._client_utils.get_ontime_for_user(x) == self._ontime)
if self._level is not None:
query = query.where(lambda x: self._levels.get_level(x) == self._level)
return self.skip_and_take(query) return self.skip_and_take(query)

View File

@ -8,11 +8,32 @@ type Mutation {
} }
type Query { type Query {
servers(filter: ServerFilter): [Server] auto_roles: [AutoRole]
server_count: Int auto_role_count: Int
auto_role_rules: [AutoRole]
auto_role_rule_count: Int
clients: [Client]
client_count: Int
known_users: [KnownUser] known_users: [KnownUser]
known_user_count: Int known_user_count: Int
levels: [Level]
level_count: Int
servers(filter: ServerFilter): [Server]
server_count: Int
user_joined_servers: [User]
user_joined_server_count: Int
user_joined_voice_channels: [User]
user_joined_voice_channel_count: Int
users(filter: UserFilter): [User]
user_count: Int
} }
type KnownUser implements TableQuery { type KnownUser implements TableQuery {
@ -38,10 +59,18 @@ type Server implements TableQuery {
id: ID id: ID
discord_id: String discord_id: String
name: String name: String
auto_roles: [AutoRole] auto_roles: [AutoRole]
auto_role_count: Int
clients: [Client] clients: [Client]
members: [User] client_count: Int
users(filter: UserFilter): [User]
user_count: Int
levels: [Level] levels: [Level]
level_count: Int
created_at: String created_at: String
modified_at: String modified_at: String
@ -69,7 +98,9 @@ type AutoRole implements TableQuery {
message_id: String message_id: String
server: Server server: Server
rules: [AutoRoleRule]
auto_role_rules: [AutoRoleRule]
auto_role_rule_count: Int
created_at: String created_at: String
modified_at: String modified_at: String
@ -109,7 +140,10 @@ type User implements TableQuery {
level: Level level: Level
joined_servers: [UserJoinedServer] joined_servers: [UserJoinedServer]
joined_server_count: Int
joined_voice_channels: [UserJoinedVoiceChannel] joined_voice_channels: [UserJoinedVoiceChannel]
joined_voice_channel_count: Int
server: Server server: Server

View File

@ -2,7 +2,6 @@ from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.model.auto_role import AutoRole from bot_data.model.auto_role import AutoRole
from bot_graphql.abc.data_query_abc import DataQueryABC from bot_graphql.abc.data_query_abc import DataQueryABC
from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter
from bot_graphql.filter.server_filter import ServerFilter from bot_graphql.filter.server_filter import ServerFilter
@ -21,7 +20,9 @@ class AutoRoleQuery(DataQueryABC):
self.set_field("channel_id", self.resolve_channel_id) self.set_field("channel_id", self.resolve_channel_id)
self.set_field("message_id", self.resolve_message_id) self.set_field("message_id", self.resolve_message_id)
self.set_field("server", self.resolve_server) self.set_field("server", self.resolve_server)
self.set_field("rules", self.resolve_rules) self._add_collection(
"auto_role_rule", lambda x, *_: self._auto_role_rules.get_auto_role_rules_by_auto_role_id(x.auto_role_id)
)
@staticmethod @staticmethod
def resolve_id(x: AutoRole, *_): def resolve_id(x: AutoRole, *_):
@ -40,9 +41,3 @@ class AutoRoleQuery(DataQueryABC):
return filter.filter(self._servers.get_server_by_id(x.server_id)) return filter.filter(self._servers.get_server_by_id(x.server_id))
return self._servers.get_server_by_id(x.server_id) return self._servers.get_server_by_id(x.server_id)
def resolve_rules(self, x: AutoRole, *_, filter: AutoRoleRuleFilter = None):
if filter is not None:
return filter.filter(self._auto_role_rules.get_auto_role_rules_by_auto_role_id(x.auto_role_id))
return self._auto_role_rules.get_auto_role_rules_by_auto_role_id(x.auto_role_id)

View File

@ -6,9 +6,6 @@ from bot_data.abc.level_repository_abc import LevelRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.server import Server from bot_data.model.server import Server
from bot_graphql.abc.data_query_abc import DataQueryABC from bot_graphql.abc.data_query_abc import DataQueryABC
from bot_graphql.abc.filter_abc import FilterABC
from bot_graphql.filter.auto_role_filter import AutoRoleFilter
from bot_graphql.filter.level_filter import LevelFilter
from bot_graphql.filter.user_filter import UserFilter from bot_graphql.filter.user_filter import UserFilter
@ -33,10 +30,15 @@ class ServerQuery(DataQueryABC):
self.set_field("id", self.resolve_id) self.set_field("id", self.resolve_id)
self.set_field("discord_id", self.resolve_discord_id) self.set_field("discord_id", self.resolve_discord_id)
self.set_field("name", self.resolve_name) self.set_field("name", self.resolve_name)
self.set_field("auto_roles", self.resolve_auto_roles)
self.set_field("clients", self.resolve_clients) self._add_collection(
self.set_field("members", self.resolve_members) "auto_role", lambda server, *_: self._auto_roles.get_auto_roles_by_server_id(server.server_id)
self.set_field("levels", self.resolve_levels) )
self._add_collection("client", lambda server, *_: self._clients.get_clients_by_server_id(server.server_id))
self._add_collection(
"user", lambda server, *_: self._users.get_users_by_server_id(server.server_id), UserFilter
)
self._add_collection("level", lambda server, *_: self._levels.get_levels_by_server_id(server.server_id))
@staticmethod @staticmethod
def resolve_id(server: Server, *_): def resolve_id(server: Server, *_):
@ -49,26 +51,3 @@ class ServerQuery(DataQueryABC):
def resolve_name(self, server: Server, *_): def resolve_name(self, server: Server, *_):
guild = self._bot.get_guild(server.discord_server_id) guild = self._bot.get_guild(server.discord_server_id)
return None if guild is None else guild.name return None if guild is None else guild.name
def resolve_auto_roles(self, server: Server, *_, filter: AutoRoleFilter = None):
if filter is not None:
return filter.filter(self._auto_roles.get_auto_roles_by_server_id(server.server_id))
return self._auto_roles.get_auto_roles_by_server_id(server.server_id)
def resolve_clients(self, server: Server, *_):
return self._clients.get_clients_by_server_id(server.server_id)
@FilterABC.resolve_filter_annotation
def resolve_members(self, server: Server, *_, filter: UserFilter = None):
if filter is not None:
return filter.filter(self._users.get_users_by_server_id(server.server_id))
return self._users.get_users_by_server_id(server.server_id)
@FilterABC.resolve_filter_annotation
def resolve_levels(self, server: Server, *_, filter: LevelFilter = None):
if filter is not None:
return filter.filter(self._levels.get_levels_by_server_id(server.server_id))
return self._levels.get_levels_by_server_id(server.server_id)

View File

@ -31,8 +31,12 @@ class UserQuery(DataQueryABC):
self.set_field("xp", self.resolve_xp) self.set_field("xp", self.resolve_xp)
self.set_field("ontime", self.resolve_ontime) self.set_field("ontime", self.resolve_ontime)
self.set_field("level", self.resolve_level) self.set_field("level", self.resolve_level)
self.set_field("joined_servers", self.resolve_joined_servers) self._add_collection(
self.set_field("joined_voice_channels", self.resolve_joined_voice_channel) "joined_server", lambda user, *_: self._ujs.get_user_joined_servers_by_user_id(user.user_id)
)
self._add_collection(
"joined_voice_channel", lambda user, *_: self._ujvs.get_user_joined_voice_channels_by_user_id(user.user_id)
)
self.set_field("server", self.resolve_server) self.set_field("server", self.resolve_server)
@staticmethod @staticmethod
@ -58,12 +62,6 @@ class UserQuery(DataQueryABC):
def resolve_level(self, user: User, *_): def resolve_level(self, user: User, *_):
return self._levels.get_level(user) return self._levels.get_level(user)
def resolve_joined_servers(self, user: User, *_):
return self._ujs.get_user_joined_servers_by_user_id(user.user_id)
def resolve_joined_voice_channel(self, user: User, *_):
return self._ujvs.get_user_joined_voice_channels_by_user_id(user.user_id)
@staticmethod @staticmethod
def resolve_server(user: User, *_): def resolve_server(user: User, *_):
return user.server return user.server

View File

@ -1,39 +1,49 @@
from ariadne import QueryType from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
from bot_data.abc.level_repository_abc import LevelRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_graphql.abc.filter_abc import FilterABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_graphql.abc.query_abc import QueryABC
from bot_graphql.filter.auto_role_filter import AutoRoleFilter
from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter
from bot_graphql.filter.level_filter import LevelFilter
from bot_graphql.filter.server_filter import ServerFilter from bot_graphql.filter.server_filter import ServerFilter
from bot_graphql.filter.user_filter import UserFilter
class Query(QueryType): class Query(QueryABC):
def __init__( def __init__(
self, self,
servers: ServerRepositoryABC, auto_roles: AutoRoleRepositoryABC,
clients: ClientRepositoryABC,
known_users: KnownUserRepositoryABC, known_users: KnownUserRepositoryABC,
levels: LevelRepositoryABC,
servers: ServerRepositoryABC,
user_joined_servers: UserJoinedServerRepositoryABC,
user_joined_voice_channel: UserJoinedVoiceChannelRepositoryABC,
users: UserRepositoryABC,
): ):
QueryType.__init__(self) QueryABC.__init__(self, "Query")
self._servers = servers self._auto_roles = auto_roles
self._clients = clients
self._known_users = known_users self._known_users = known_users
self._levels = levels
self._servers = servers
self._user_joined_servers = user_joined_servers
self._user_joined_voice_channels = user_joined_voice_channel
self._users = users
self.set_field("servers", self.resolve_servers) self._add_collection("auto_role", lambda *_: self._auto_roles.get_auto_roles(), AutoRoleFilter)
self.set_field("server_count", self.resolve_server_count) self._add_collection("auto_role_rule", lambda *_: self._auto_roles.get_auto_role_rules(), AutoRoleRuleFilter)
self._add_collection("client", lambda *_: self._clients.get_clients())
self.set_field("known_users", self.resolve_known_users) self._add_collection("known_user", lambda *_: self._known_users.get_users())
self.set_field("known_user_count", self.resolve_known_users_count) self._add_collection("level", lambda *_: self._levels.get_levels(), LevelFilter)
self._add_collection("server", lambda *_: self._servers.get_servers(), ServerFilter)
@FilterABC.resolve_filter_annotation self._add_collection("user_joined_server", lambda *_: self._user_joined_servers.get_user_joined_servers())
def resolve_servers(self, *_, filter: ServerFilter = None): self._add_collection(
if filter is not None: "user_joined_voice_channel", lambda *_: self._user_joined_voice_channels.get_user_joined_voice_channels()
return filter.filter(self._servers.get_servers()) )
else: self._add_collection("user", lambda *_: self._users.get_users(), UserFilter)
return self._servers.get_servers()
def resolve_server_count(self, *_):
return self._servers.get_servers().count()
def resolve_known_users(self, *_):
return self._known_users.get_users()
def resolve_known_users_count(self, *_):
return self._known_users.get_users().count()