Coverage for functions \ flipdare \ service \ chat_service.py: 88%
59 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
13from __future__ import annotations
15from typing import TYPE_CHECKING
16from flipdare.app_log import LOG
17from flipdare.constants import IS_DEBUG
19from flipdare.result.app_result import AppResult
20from flipdare.core.job_type_decorator import job_type_decorator
21from flipdare.result.job_result import JobResult
22from flipdare.core.trigger_decorator import trigger_decorator
23from flipdare.generated import AppErrorCode, AppJobType
24from flipdare.message.user_message import UserMessage
25from flipdare.service.safety.core.restriction_calculator import ChatRestrictionCalculator
26from flipdare.generated.shared.firestore_collections import FirestoreCollections
27from flipdare.service._service_provider import ServiceProvider
28from flipdare.wrapper import AppJobWrapper, ChatCommentWrapper
30if TYPE_CHECKING:
31 from flipdare.manager.service_manager import ServiceManager
32 from flipdare.manager.db_manager import DbManager
33 from flipdare.manager.backend_manager import BackendManager
35__all__ = ["ChatService"]
37_JT = AppJobType
38_COL = FirestoreCollections.CHAT
41class ChatService(ServiceProvider):
43 def __init__(
44 self,
45 db_manager: DbManager | None = None,
46 service_manager: ServiceManager | None = None,
47 backend_manager: BackendManager | None = None,
48 ) -> None:
49 super().__init__(
50 backend_manager=backend_manager,
51 service_manager=service_manager,
52 db_manager=db_manager,
53 )
55 @job_type_decorator(_JT.TR_CHAT)
56 @trigger_decorator(job_type=_JT.TR_CHAT, collection=_COL, wrapper_class=ChatCommentWrapper)
57 def trigger_chat(
58 self,
59 job: AppJobWrapper,
60 *,
61 wrapper: ChatCommentWrapper,
62 ) -> JobResult[ChatCommentWrapper]:
63 """
64 Check if the user has a bad reputation, if so, perform
65 moderation on the chat comment to check if it needs to be flagged.
66 """
67 job_id = job.doc_id
68 comment_id = wrapper.doc_id
70 main_result = AppResult[ChatCommentWrapper](
71 task_name=f"chat comment {comment_id}", doc_id=job_id
72 )
73 parent_id = job.parent_obj_id
74 if parent_id is None:
75 msg = f"Missing parent_obj_id in job {job.doc_id}: {job.obj_id}"
76 LOG().error(msg)
77 main_result.add_error(AppErrorCode.INVALID_INPUT, msg)
78 return JobResult.from_result(main_result, data=job.to_json_dict())
80 try:
81 outcome = self.moderation_service.review_comment(wrapper)
82 if outcome.is_approved:
83 # Auto-approved
84 if IS_DEBUG:
85 LOG().debug(f"Comment auto-approved: {job_id}")
87 return JobResult.ok(doc_id=job_id, message="Comment auto-approved.")
88 """
89 Process:
90 1. decrease reputation of user (should already be done in review_comment)
91 2. Block the message is the reputation is low enough
92 """
93 calculator = ChatRestrictionCalculator(
94 decision=outcome.decision,
95 reputation=outcome.new_reputation,
96 )
97 should_block = calculator.should_block()
98 if not should_block:
99 if IS_DEBUG:
100 LOG().debug(f"Comment requires review but not blocked: {job_id}")
101 return JobResult.ok(
102 doc_id=job_id,
103 message="Comment requires review but not blocked.",
104 )
106 # Block the comment
107 category = outcome.assessment.moderation_category if outcome.assessment else None
108 if category is None:
109 # assume safe if no category
110 if IS_DEBUG:
111 LOG().debug(f"No moderation category for comment {job_id}, cannot block.")
112 return JobResult.ok(
113 doc_id=job_id,
114 message="No moderation category, cannot block comment.",
115 )
117 block_message = UserMessage.chat_block(category_label=category.label)
118 wrapper.admin_block_reason = block_message
120 self.db_manager.chat_db.update_comment(parent_id=parent_id, comment=wrapper)
122 LOG().info(f"Comment blocked: {job_id} Reason: {block_message}")
123 return JobResult.ok(doc_id=job_id, message="Comment blocked due to moderation.")
124 except Exception as e:
125 cause = f"Error processing comment: {e}"
126 main_result.add_error(AppErrorCode.MODERATION, cause)
127 return JobResult.from_result(main_result, data=wrapper.to_json_dict())