diff --git a/time_tracker_api/time_entries/time_entries_model.py b/time_tracker_api/time_entries/time_entries_model.py index 5e917405..20cb6f5b 100644 --- a/time_tracker_api/time_entries/time_entries_model.py +++ b/time_tracker_api/time_entries/time_entries_model.py @@ -1,10 +1,9 @@ import abc from dataclasses import dataclass, field from typing import List, Callable - from azure.cosmos import PartitionKey from azure.cosmos.exceptions import CosmosResourceNotFoundError - +from flask_restplus import abort from flask_restplus._http import HTTPStatus from commons.data_access_layer.cosmos_db import ( @@ -22,10 +21,16 @@ from commons.data_access_layer.database import EventContext from time_tracker_api.activities import activities_model -from utils.extend_model import add_project_name_to_time_entries, add_activity_name_to_time_entries +from utils.extend_model import ( + add_project_name_to_time_entries, + add_activity_name_to_time_entries, +) from utils import worked_time from utils.worked_time import str_to_datetime - +from utils.extend_model import ( + create_in_condition, + create_custom_query_from_str, +) from time_tracker_api.projects.projects_model import ProjectCosmosDBModel from time_tracker_api.projects import projects_model from time_tracker_api.database import CRUDDao, APICosmosDBDao @@ -144,13 +149,12 @@ def find_all( self, event_context: EventContext, conditions: dict = {}, + custom_sql_conditions: List[str] = [], date_range: dict = {}, ): - custom_sql_conditions = [self.create_sql_date_range_filter(date_range)] - - if event_context.is_admin: - conditions.pop("owner_id") - # TODO should be removed when implementing a role-based permission module ↑ + custom_sql_conditions.append( + self.create_sql_date_range_filter(date_range) + ) custom_params = self.generate_params(date_range) time_entries = CosmosDBRepository.find_all( @@ -162,14 +166,8 @@ def find_all( ) if time_entries: - projects_id = [str(project.project_id) for project in time_entries] - p_ids = ( - str(tuple(projects_id)).replace(",", "") - if len(projects_id) == 1 - else str(tuple(projects_id)) - ) - custom_conditions = "c.id IN {}".format(p_ids) - # TODO this must be refactored to be used from the utils module ↑ + custom_conditions = create_in_condition(time_entries, "project_id") + project_dao = projects_model.create_dao() projects = project_dao.get_all( custom_sql_conditions=[custom_conditions] @@ -343,10 +341,30 @@ def stop_time_entry_if_was_left_running( def get_all(self, conditions: dict = None, **kwargs) -> list: event_ctx = self.create_event_context("read-many") conditions.update({"owner_id": event_ctx.user_id}) - + custom_query = [] + if "user_id" in conditions: + if event_ctx.is_admin: + conditions.pop("owner_id") + custom_query = ( + [] + if conditions.get("user_id") == "*" + else [ + create_custom_query_from_str( + conditions.get("user_id"), "c.owner_id" + ) + ] + ) + conditions.pop("user_id") + else: + abort( + HTTPStatus.FORBIDDEN, "You don't have enough permissions." + ) date_range = self.handle_date_filter_args(args=conditions) return self.repository.find_all( - event_ctx, conditions=conditions, date_range=date_range + event_ctx, + conditions=conditions, + custom_sql_conditions=custom_query, + date_range=date_range, ) def get(self, id): @@ -432,6 +450,8 @@ def get_worked_time(self, conditions: dict = {}): @staticmethod def handle_date_filter_args(args: dict) -> dict: date_range = None + year = None + month = None if 'month' and 'year' in args: month = int(args.get("month")) year = int(args.get("year")) diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index 8275e41d..aae7e6ff 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -143,6 +143,14 @@ ) # custom attributes filter +attributes_filter.add_argument( + 'user_id', + required=False, + store_missing=False, + help="(Filter) User to filter by", + location='args', +) + attributes_filter.add_argument( 'month', required=False, @@ -150,6 +158,7 @@ help="(Filter) Month to filter by", location='args', ) + attributes_filter.add_argument( 'year', required=False, @@ -165,6 +174,7 @@ help="(Filter) Start to filter by", location='args', ) + attributes_filter.add_argument( 'end_date', required=False, diff --git a/utils/extend_model.py b/utils/extend_model.py index 25a79b2a..356f133c 100644 --- a/utils/extend_model.py +++ b/utils/extend_model.py @@ -1,3 +1,6 @@ +import re + + def add_customer_name_to_projects(projects, customers): """ Add attribute customer_name in project model, based on customer_id of the @@ -32,4 +35,46 @@ def add_activity_name_to_time_entries(time_entries, activities): for time_entry in time_entries: for activity in activities: if time_entry.activity_id == activity.id: - setattr(time_entry, 'activity_name', activity.name) \ No newline at end of file + setattr(time_entry, 'activity_name', activity.name) + + +def create_in_condition( + data_object: list, attr_to_filter: str = "", first_attr: str = "c.id" +): + """ + Function to create a custom query string from a list of objects or a list of strings. + :param data_object: List of objects or a list of strings + :param attr_to_filter: Attribute to retrieve the value of the objects (Only in case it is a list of objects) + :param first_attr: First attribute to build the condition + :return: Custom condition string + """ + attr_filter = re.sub('[^a-zA-Z_$0-9]', '', attr_to_filter) + object_id = ( + [str(i) for i in data_object] + if type(data_object[0]) == str + else [str(eval(f"object.{attr_filter}")) for object in data_object] + ) + ids = ( + str(tuple(object_id)).replace(",", "") + if len(object_id) == 1 + else str(tuple(object_id)) + ) + return "{} IN {}".format(first_attr, ids) + + +def create_custom_query_from_str( + data: str, first_attr, delimiter: str = "," +) -> str: + """ + Function to create a string condition for url parameters (Example: data?values=value1,value2 or data?values=*) + :param data: String to build the query + :param first_attr: First attribute to build the condition + :param delimiter: String delimiter + :return: Custom condition string + """ + data = data.split(delimiter) + if len(data) > 1: + query_str = create_in_condition(data, first_attr=first_attr) + else: + query_str = "{} = '{}'".format(first_attr, data[0]) + return query_str