def is_user_permitted_to_map(project_id: int, user_id: int):
        """ Check if the user is allowed to map the on the project in scope """
        if UserService.is_user_blocked(user_id):
            return False, MappingNotAllowed.USER_NOT_ON_ALLOWED_LIST

        project = ProjectService.get_project_by_id(project_id)

        if ProjectStatus(project.status) != ProjectStatus.PUBLISHED and not UserService.is_user_a_project_manager(user_id):
            return False, MappingNotAllowed.PROJECT_NOT_PUBLISHED

        tasks = project.get_locked_tasks_for_user(user_id)

        if len(tasks) > 0:
            return False, MappingNotAllowed.USER_ALREADY_HAS_TASK_LOCKED

        if project.enforce_mapper_level:
            if not ProjectService._is_user_mapping_level_at_or_above_level_requests(MappingLevel(project.mapper_level),
                                                                                    user_id):
                return False, MappingNotAllowed.USER_NOT_CORRECT_MAPPING_LEVEL

        if project.license_id:
            if not UserService.has_user_accepted_license(user_id, project.license_id):
                return False, MappingNotAllowed.USER_NOT_ACCEPTED_LICENSE

        if project.private:
            # Check user is in allowed users
            try:
                next(user for user in project.allowed_users if user.id == user_id)
            except StopIteration:
                return False, MappingNotAllowed.USER_NOT_ON_ALLOWED_LIST

        return True, 'User allowed to map'
Beispiel #2
0
    def update_stats_after_task_state_change(project_id: int, user_id: int,
                                             new_state: TaskStatus,
                                             task_id: int):
        """ Update stats when a task has had a state change """
        if new_state in [
                TaskStatus.READY, TaskStatus.LOCKED_FOR_VALIDATION,
                TaskStatus.LOCKED_FOR_MAPPING
        ]:
            return  # No stats to record for these states

        project = ProjectService.get_project_by_id(project_id)
        user = UserService.get_user_by_id(user_id)

        if new_state == TaskStatus.MAPPED:
            StatsService._set_counters_after_mapping(project, user)
        elif new_state == TaskStatus.INVALIDATED:
            StatsService._set_counters_after_invalidated(
                task_id, project, user)
        elif new_state == TaskStatus.VALIDATED:
            StatsService._set_counters_after_validated(project, user)
        elif new_state == TaskStatus.BADIMAGERY:
            StatsService._set_counters_after_bad_imagery(project)

        UserService.upsert_mapped_projects(user_id, project_id)
        project.last_updated = timestamp()

        # Transaction will be saved when task is saved
        return project, user
    def is_user_permitted_to_validate(project_id, user_id):
        """ Check if the user is allowed to validate on the project in scope """
        if UserService.is_user_blocked(user_id):
            return False, ValidatingNotAllowed.USER_NOT_ON_ALLOWED_LIST

        project = ProjectService.get_project_by_id(project_id)

        if ProjectStatus(
                project.status
        ) != ProjectStatus.PUBLISHED and not UserService.is_user_a_project_manager(
                user_id):
            return False, ValidatingNotAllowed.PROJECT_NOT_PUBLISHED

        if project.enforce_validator_role and not UserService.is_user_validator(
                user_id):
            return False, ValidatingNotAllowed.USER_NOT_VALIDATOR

        if project.license_id:
            if not UserService.has_user_accepted_license(
                    user_id, project.license_id):
                return False, ValidatingNotAllowed.USER_NOT_ACCEPTED_LICENSE

        if project.private:
            # Check user is in allowed users
            try:
                next(user for user in project.allowed_users
                     if user.id == user_id)
            except StopIteration:
                return False, ValidatingNotAllowed.USER_NOT_ON_ALLOWED_LIST

        return True, 'User allowed to validate'
Beispiel #4
0
    def put(self, username):
        """
        Creates user
        ---
        tags:
          - admin - users
        produces:
          - application/json
        parameters:
          - in: header
            name: Authorization
            description: Base64 encoded session token
            required: true
            type: string
          - in: body
            name: body
            required: true
            description: JSON object for creating a new user
            schema:
                  properties:
                      password:
                          type: string
                          default: password
                      role:
                          type: string
                          default: user
          - in: path
            name: username
            description: the unique user
            required: true
            type: string
            default: dmisuser
        responses:
          201:
            description: User Created
          400:
            description: Invalid request
          401:
            description: Unauthorized, credentials are invalid
          403:
            description: Forbidden, username already exists
          500:
            description: Internal Server Error
        """
        try:
            user_dto = UserDTO(request.get_json())
            user_dto.username = username
            user_dto.validate()
        except DataError as e:
            current_app.logger.error(f'error validating request: {str(e)}')
            return str(e), 400

        try:
            UserService.create_user(user_dto)
        except UserExistsError as e:
            return {"Error": str(e)}, 403
        except Exception as e:
            error_msg = f'User Create - Unhandled error: {str(e)}'
            current_app.logger.critical(error_msg)
            return {"Error": error_msg}, 500
Beispiel #5
0
    def login_user(osm_user_details, redirect_to, user_element='user') -> str:
        """
        Generates authentication details for user, creating in DB if user is unknown to us
        :param osm_user_details: XML response from OSM
        :param redirect_to: Route to redirect user to, from callback url
        :param user_element: Exists for unit testing
        :raises AuthServiceError
        :returns Authorized URL with authentication details in query string
        """
        osm_user = osm_user_details.find(user_element)

        if osm_user is None:
            raise AuthServiceError('User element not found in OSM response')

        osm_id = int(osm_user.attrib['id'])
        username = osm_user.attrib['display_name']

        try:
            UserService.get_user_by_id(osm_id)
        except NotFound:
            # User not found, so must be new user
            changesets = osm_user.find('changesets')
            changeset_count = int(changesets.attrib['count'])
            new_user = UserService.register_user(osm_id, username,
                                                 changeset_count)
            MessageService.send_welcome_message(new_user)

        session_token = AuthenticationService.generate_session_token_for_user(
            osm_id)
        authorized_url = AuthenticationService.generate_authorized_url(
            username, session_token, redirect_to)

        return authorized_url
    def update_stats_after_task_state_change(
        project_id: int,
        user_id: int,
        last_state: TaskStatus,
        new_state: TaskStatus,
        action="change",
    ):
        """ Update stats when a task has had a state change """

        if new_state in [
                TaskStatus.READY,
                TaskStatus.LOCKED_FOR_VALIDATION,
                TaskStatus.LOCKED_FOR_MAPPING,
        ]:
            return  # No stats to record for these states

        project = ProjectService.get_project_by_id(project_id)
        user = UserService.get_user_by_id(user_id)

        project, user = StatsService._update_tasks_stats(
            project, user, last_state, new_state, action)
        UserService.upsert_mapped_projects(user_id, project_id)
        project.last_updated = timestamp()

        # Transaction will be saved when task is saved
        return project, user
    def test_user_service_raise_error_if_user_element_not_found(self):
        # Arrange
        osm_response = get_canned_simplified_osm_user_details()

        # Act / Assert
        with self.assertRaises(UserServiceError):
            UserService._parse_osm_user_details_response(
                osm_response, 'wont-find')
Beispiel #8
0
    def test_pm_not_allowed_to_add_admin_role_when_setting_role(
            self, mock_admin):
        # Arrange
        admin = User()
        admin.role = UserRole.PROJECT_MANAGER.value
        mock_admin.return_value = admin

        # Act
        with self.assertRaises(UserServiceError):
            UserService.add_role_to_user(1, 'test', 'ADMIN')
Beispiel #9
0
    def test_get_user_by_id_returns_not_found_exception(self):
        """ Get a canned user from the DB """
        if self.skip_tests:
            return

        # Arrange
        test_user_id = 9999999999999999999

        # Act
        with self.assertRaises(NotFound):
            UserService.get_user_by_id(test_user_id)
Beispiel #10
0
    def test_get_user_by_username_returns_not_found_exception(self):
        """ Check that NotFound exception returned if username not found """
        if self.skip_tests:
            return

        # Arrange
        test_username = '******'

        # Act
        with self.assertRaises(NotFound):
            UserService.get_user_by_username(test_username)
    def test_upsert_inserts_project_if_not_exists(self):
        if self.skip_tests:
            return

        # Arrange
        UserService.upsert_mapped_projects(self.test_user.id, self.test_project.id)

        # Act
        projects = UserService.get_mapped_projects(self.test_user.username, 'en')

        # Assert
        mapped_project = projects.mapped_projects[0]
        self.assertEqual(mapped_project.project_id, self.test_project.id)  # We should find we've mapped the test project
Beispiel #12
0
 def post(self, username, role):
     """
     Allows PMs to set the users role
     ---
     tags:
       - user
     produces:
       - application/json
     parameters:
         - in: header
           name: Authorization
           description: Base64 encoded session token
           required: true
           type: string
           default: Token sessionTokenHere==
         - name: username
           in: path
           description: The users username
           required: true
           type: string
           default: Thinkwhere
         - name: role
           in: path
           description: The role to add
           required: true
           type: string
           default: ADMIN
     responses:
         200:
             description: Role set
         401:
             description: Unauthorized - Invalid credentials
         403:
             description: Forbidden
         404:
             description: User not found
         500:
             description: Internal Server Error
     """
     try:
         UserService.add_role_to_user(tm.authenticated_user_id, username,
                                      role)
         return {"Success": "Role Added"}, 200
     except UserServiceError:
         return {"Error": "Not allowed"}, 403
     except NotFound:
         return {"Error": "User or mapping not found"}, 404
     except Exception as e:
         error_msg = f'User GET - unhandled error: {str(e)}'
         current_app.logger.critical(error_msg)
         return {"error": error_msg}, 500
Beispiel #13
0
 def post(self, username, level):
     """
     Allows PMs to set a users mapping level
     ---
     tags:
       - user
     produces:
       - application/json
     parameters:
         - in: header
           name: Authorization
           description: Base64 encoded session token
           required: true
           type: string
           default: Token sessionTokenHere==
         - name: username
           in: path
           description: The users username
           required: true
           type: string
           default: Thinkwhere
         - name: level
           in: path
           description: The mapping level that should be set
           required: true
           type: string
           default: ADVANCED
     responses:
         200:
             description: Level set
         400:
             description: Bad Request - Client Error
         401:
             description: Unauthorized - Invalid credentials
         404:
             description: User not found
         500:
             description: Internal Server Error
     """
     try:
         UserService.set_user_mapping_level(username, level)
         return {"Success": "Level set"}, 200
     except UserServiceError:
         return {"Error": "Not allowed"}, 400
     except NotFound:
         return {"Error": "User or mapping not found"}, 404
     except Exception as e:
         error_msg = f'User GET - unhandled error: {str(e)}'
         current_app.logger.critical(error_msg)
         return {"error": error_msg}, 500
Beispiel #14
0
    def test_mapper_level_updates_correctly(self, mock_user, mock_osm, mock_save):
        # Arrange
        test_user = User()
        test_user.mapping_level = MappingLevel.BEGINNER.value
        mock_user.return_value = test_user

        test_osm = UserOSMDTO()
        test_osm.changeset_count = 350
        mock_osm.return_value = test_osm

        # Act
        UserService.check_and_update_mapper_level(123)

        #Assert
        self.assertTrue(test_user.mapping_level, MappingLevel.INTERMEDIATE.value)
Beispiel #15
0
    def send_message_to_all_contributors(project_id: int,
                                         message_dto: MessageDTO):
        """  Sends supplied message to all contributors on specified project.  Message all contributors can take
             over a minute to run, so this method is expected to be called on its own thread """

        app = create_app(
        )  # Because message-all run on background thread it needs it's own app context

        with app.app_context():
            contributors = Message.get_all_contributors(project_id)

            project_link = MessageService.get_project_link(project_id)

            message_dto.message = f'{project_link}<br/><br/>' + message_dto.message  # Append project link to end of message

            msg_count = 0
            for contributor in contributors:
                message = Message.from_dto(contributor[0], message_dto)
                message.save()
                user = UserService.get_user_by_id(contributor[0])
                SMTPService.send_email_alert(user.email_address, user.username)
                msg_count += 1
                if msg_count == 10:
                    time.sleep(
                        0.5
                    )  # Sleep for 0.5 seconds to avoid hitting AWS rate limits every 10 messages
                    msg_count = 0
 def get_project_user_stats(project_id: int,
                            username: str) -> ProjectUserStatsDTO:
     """ Gets the user stats for a specific project """
     print(project_id)
     project = ProjectService.get_project_by_id(project_id)
     user = UserService.get_user_by_username(username)
     return project.get_project_user_stats(user.id)
    def post(self):
        """
        Updates user info
        ---
        tags:
          - user
        produces:
          - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - in: body
              name: body
              required: true
              description: JSON object for creating draft project
              schema:
                  properties:
                      emailAddress:
                          type: string
                          default: [email protected]
                      twitterId:
                          type: string
                          default: tweeter
                      facebookId:
                          type: string
                          default: fbme
                      linkedinId:
                          type: string
                          default: linkme
        responses:
            200:
                description: Details saved
            400:
                description: Client Error - Invalid Request
            401:
                description: Unauthorized - Invalid credentials
            500:
                description: Internal Server Error
        """
        try:
            user_dto = UserDTO(request.get_json())
            user_dto.validate()
        except DataError as e:
            current_app.logger.error(f'error validating request: {str(e)}')
            return str(e), 400

        try:
            verification_sent = UserService.update_user_details(
                tm.authenticated_user_id, user_dto)
            return verification_sent, 200
        except NotFound:
            return {"Error": "User not found"}, 404
        except Exception as e:
            error_msg = f'User GET - unhandled error: {str(e)}'
            current_app.logger.critical(error_msg)
            return {"error": error_msg}, 500
    def is_valid_token(token, token_expiry):
        """
        Validates if the supplied token is valid, and hasn't expired.
        :param token: Token to check
        :param token_expiry: When the token expires
        :return: True if token is valid
        """
        serializer = URLSafeTimedSerializer(current_app.secret_key)

        try:
            tokenised_user_id = serializer.loads(token, max_age=token_expiry)
            dmis.authenticated_user_id = tokenised_user_id
        except SignatureExpired:
            current_app.logger.debug('Token has expired')
            return False
        except BadSignature:
            current_app.logger.debug('Bad Token Signature')
            return False

        # Ensure user is Admin priv to access admin only endpoints
        if dmis.is_admin_only_resource:
            user = UserService.get_user_by_id(tokenised_user_id)
            if UserRole(user.role) != UserRole.ADMIN:
                current_app.logger.debug(f'User {tokenised_user_id} is not an Admin {request.base_url}')
                return False

        return True
Beispiel #19
0
 def get(self):
     """
     Gets a list of all users
     ---
     tags:
       - admin - users
     produces:
       - application/json
     parameters:
       - in: header
         name: Authorization
         description: Base64 encoded session token
         required: true
         type: string
     responses:
       200:
         description: Users found
       401:
         description: Unauthorized, credentials are invalid
       500:
         description: Internal Server Error
     """
     try:
         user_dto = UserService.get_all_users()
         return user_dto.to_primitive(), 200
     except Exception as e:
         error_msg = f'User list GET - unhandled error: {str(e)}'
         current_app.logger.critical(error_msg)
         return {"error": error_msg}, 500
Beispiel #20
0
    def send_message_after_comment(comment_from: int, comment: str,
                                   task_id: int, project_id: int):
        """ Will send a canned message to anyone @'d in a comment """
        usernames = MessageService._parse_message_for_username(comment)

        if len(usernames) == 0:
            return  # Nobody @'d so return

        task_link = MessageService.get_task_link(project_id, task_id)
        project_title = ProjectService.get_project_title(project_id)
        for username in usernames:

            try:
                user = UserService.get_user_by_username(username)
            except NotFound:
                current_app.logger.error(f'Username {username} not found')
                continue  # If we can't find the user, keep going no need to fail

            message = Message()
            message.from_user_id = comment_from
            message.to_user_id = user.id
            message.subject = f'You were mentioned in a comment in Project {project_id} on {task_link}'
            message.message = comment
            message.add_message()
            SMTPService.send_email_alert(user.email_address, user.username)
Beispiel #21
0
    def send_message_after_chat(chat_from: int, chat: str, project_id: int):
        """ Send alert to user if they were @'d in a chat message """
        current_app.logger.debug('Sending Message After Chat')
        usernames = MessageService._parse_message_for_username(chat)

        if len(usernames) == 0:
            return  # Nobody @'d so return

        link = MessageService.get_project_link(project_id)

        for username in usernames:
            current_app.logger.debug(f'Searching for {username}')
            try:
                user = UserService.get_user_by_username(username)
            except NotFound:
                current_app.logger.error(f'Username {username} not found')
                continue  # If we can't find the user, keep going no need to fail

            message = Message()
            message.from_user_id = chat_from
            message.to_user_id = user.id
            message.subject = f'You were mentioned in Project Chat on {link}'
            message.message = chat
            message.add_message()
            SMTPService.send_email_alert(user.email_address, user.username)
Beispiel #22
0
    def send_message_after_validation(status: int, validated_by: int,
                                      mapped_by: int, task_id: int,
                                      project_id: int):
        """ Sends mapper a notification after their task has been marked valid or invalid """
        if validated_by == mapped_by:
            return  # No need to send a message to yourself

        user = UserService.get_user_by_id(mapped_by)
        if user.validation_message == False:
            return  # No need to send validation message

        text_template = get_template('invalidation_message_en.txt' if status == TaskStatus.INVALIDATED \
                                                                   else 'validation_message_en.txt')
        status_text = 'marked invalid' if status == TaskStatus.INVALIDATED else 'validated'
        task_link = MessageService.get_task_link(project_id, task_id)

        text_template = text_template.replace('[USERNAME]', user.username)
        text_template = text_template.replace('[TASK_LINK]', task_link)

        validation_message = Message()
        validation_message.from_user_id = validated_by
        validation_message.to_user_id = mapped_by
        validation_message.subject = f'Your mapping in Project {project_id} on {task_link} has just been {status_text}'
        validation_message.message = text_template
        validation_message.add_message()

        SMTPService.send_email_alert(user.email_address, user.username)
 def get(self, username):
     """
     Get detailed stats about user
     ---
     tags:
       - user
     produces:
       - application/json
     parameters:
         - name: username
           in: path
           description: The users username
           required: true
           type: string
           default: Thinkwhere
     responses:
         200:
             description: User found
         404:
             description: User not found
         500:
             description: Internal Server Error
     """
     try:
         stats_dto = UserService.get_detailed_stats(username)
         return stats_dto.to_primitive(), 200
     except NotFound:
         return {"Error": "User not found"}, 404
     except Exception as e:
         error_msg = f'User GET - unhandled error: {str(e)}'
         current_app.logger.critical(error_msg)
         return {"error": error_msg}, 500
Beispiel #24
0
def verify_token(token):
    """ Verify the supplied token and check user role is correct for the requested resource"""

    if not token:
        current_app.logger.debug(f'Token not supplied {request.base_url}')
        return False

    try:
        decoded_token = base64.b64decode(token).decode('utf-8')
    except UnicodeDecodeError:
        current_app.logger.debug(f'Unable to decode token {request.base_url}')
        return False  # Can't decode token, so fail login

    valid_token, user_id = AuthenticationService.is_valid_token(
        decoded_token, 604800)
    if not valid_token:
        current_app.logger.debug(f'Token not valid {request.base_url}')
        return False

    if tm.is_pm_only_resource:
        if not UserService.is_user_a_project_manager(user_id):
            current_app.logger.debug(
                f'User {user_id} is not a PM {request.base_url}')
            return False

    current_app.logger.debug(
        f'Validated user {user_id} for {request.base_url}')
    tm.authenticated_user_id = user_id  # Set the user ID on the decorator as a convenience
    return True  # All tests passed token is good for the requested resource
Beispiel #25
0
    def set_counters_after_undo(project_id: int, user_id: int, current_state: TaskStatus, undo_state: TaskStatus):
        """ Resets counters after a user undoes their task"""
        project = ProjectService.get_project_by_id(project_id)
        user = UserService.get_user_by_id(user_id)

        # This is best endeavours to reset the stats and may have missed some edge cases, hopefully majority of
        # cases will be Mapped to Ready
        if current_state == TaskStatus.MAPPED and undo_state == TaskStatus.READY:
            project.tasks_mapped -= 1
            user.tasks_mapped -= 1
        if current_state == TaskStatus.MAPPED and undo_state == TaskStatus.INVALIDATED:
            user.tasks_mapped -= 1
            project.tasks_mapped -= 1
        elif current_state == TaskStatus.BADIMAGERY and undo_state == TaskStatus.READY:
            project.tasks_bad_imagery -= 1
        elif current_state == TaskStatus.BADIMAGERY and undo_state == TaskStatus.MAPPED:
            project.tasks_mapped += 1
            project.tasks_bad_imagery -= 1
        elif current_state == TaskStatus.BADIMAGERY and undo_state == TaskStatus.INVALIDATED:
            project.tasks_bad_imagery -= 1
        elif current_state == TaskStatus.INVALIDATED and undo_state == TaskStatus.MAPPED:
            user.tasks_invalidated -= 1
            project.tasks_mapped += 1
        elif current_state == TaskStatus.INVALIDATED and undo_state == TaskStatus.VALIDATED:
            user.tasks_invalidated -= 1
            project.tasks_validated += 1
        elif current_state == TaskStatus.VALIDATED and undo_state == TaskStatus.MAPPED:
            user.tasks_validated -= 1
            project.tasks_validated -= 1
        elif current_state == TaskStatus.VALIDATED and undo_state == TaskStatus.BADIMAGERY:
            user.tasks_validated -= 1
            project.tasks_validated -= 1
    def test_user_can_register_with_correct_mapping_level(self, mock_user):
        # Act
        test_user = UserService().register_user(12, 'Thinkwhere', 300)

        # Assert
        self.assertEqual(test_user.mapping_level,
                         MappingLevel.INTERMEDIATE.value)
Beispiel #27
0
    def test_admin_role_is_recognized_as_a_validator(self, mock_user):
        # Arrange
        stub_user = User()
        stub_user.role = UserRole.ADMIN.value
        mock_user.return_value = stub_user

        # Act / Assert
        self.assertTrue(UserService.is_user_validator(123))
Beispiel #28
0
    def test_mapper_role_is_not_recognized_as_a_validator(self, mock_user):
        # Arrange
        stub_user = User()
        stub_user.role = UserRole.MAPPER.value
        mock_user.return_value = stub_user

        # Act / Assert
        self.assertFalse(UserService.is_user_validator(123))
Beispiel #29
0
    def post(self, username):
        """
        Updates a user
        ---
        tags:
          - admin - users
        produces:
          - application/json
        parameters:
          - in: header
            name: Authorization
            description: Base64 encoded session token
            required: true
            type: string
          - in: body
            name: body
            required: true
            description: JSON object for creating a new user
            schema:
                  properties:
                      role:
                          type: string
                          default: user
          - in: path
            name: username
            description: the unique user
            required: true
            type: string
            default: dmisuser
        responses:
          200:
            description: User details updated
          400:
            description: Invalid request
          401:
            description: Unauthorized, credentials are invalid
          404:
            description: Not found
          500:
            description: Internal Server Error
        """
        try:
            user_update_dto = UserUpdateDTO(request.get_json())
            user_update_dto.username = username
            user_update_dto.validate()
        except DataError as e:
            current_app.logger.error(f'error validating request: {str(e)}')
            return str(e), 400

        try:
            updated_user = UserService.update_user(user_update_dto)
            return updated_user.to_primitive(), 200
        except NotFound:
            return {"Error": "User not found"}, 404
        except Exception as e:
            error_msg = f'User update - Unhandled error: {str(e)}'
            current_app.logger.critical(error_msg)
            return {"Error": error_msg}, 500
Beispiel #30
0
    def put(self, project_id):
        """
        Add a message to project chat
        ---
        tags:
          - messaging
        produces:
          - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              required: true
              type: string
              default: Token sessionTokenHere==
            - name: project_id
              in: path
              description: The ID of the project to attach the chat message to
              required: true
              type: integer
              default: 1
            - in: body
              name: body
              required: true
              description: JSON object for creating a new mapping license
              schema:
                  properties:
                      message:
                          type: string
                          default: This is an awesome project
        responses:
            201:
                description: Message posted successfully
            400:
                description: Invalid Request
            500:
                description: Internal Server Error
        """

        if UserService.is_user_blocked(tm.authenticated_user_id):
            return 'User is on read only mode', 403

        try:
            chat_dto = ChatMessageDTO(request.get_json())
            chat_dto.user_id = tm.authenticated_user_id
            chat_dto.project_id = project_id
            chat_dto.validate()
        except DataError as e:
            current_app.logger.error(f'Error validating request: {str(e)}')
            return str(e), 400

        try:
            project_messages = ChatService.post_message(chat_dto)
            return project_messages.to_primitive(), 201
        except Exception as e:
            error_msg = f'Chat PUT - unhandled error: {str(e)}'
            current_app.logger.critical(error_msg)
            return {"error": error_msg}, 500