1+ import functools
12import logging
3+ from typing import Any
24
35from aiohttp import web
6+ from common_library .json_serialization import json_dumps
47from models_library .api_schemas_webserver .conversations import (
58 ConversationMessagePatch ,
69 ConversationMessageRestGet ,
1619 PageQueryParameters ,
1720)
1821from models_library .rest_pagination_utils import paginate_data
22+ from models_library .utils .fastapi_encoders import jsonable_encoder
1923from pydantic import BaseModel , ConfigDict
2024from servicelib .aiohttp import status
2125from servicelib .aiohttp .requests_validation import (
2529)
2630from servicelib .mimetype_constants import MIMETYPE_APPLICATION_JSON
2731from servicelib .rest_constants import RESPONSE_MODEL_POLICY
32+ from simcore_service_webserver .users import users_service
2833
2934from ..._meta import API_VTAG as VTAG
35+ from ...email import email_service
3036from ...login .decorators import login_required
3137from ...models import AuthenticatedRequestContext
32- from ...users import users_service
38+ from ...products import products_web
3339from ...utils_aiohttp import envelope_json_response
3440from .. import _conversation_message_service , _conversation_service
3541from ._common import ConversationPathParams , raise_unsupported_type
@@ -56,6 +62,10 @@ class _ConversationMessageCreateBodyParams(BaseModel):
5662 model_config = ConfigDict (extra = "forbid" )
5763
5864
65+ def _json_encoder_and_dumps (obj : Any , ** kwargs ):
66+ return json_dumps (jsonable_encoder (obj ), ** kwargs )
67+
68+
5969@routes .post (
6070 f"/{ VTAG } /conversations/{{conversation_id}}/messages" ,
6171 name = "create_conversation_message" ,
@@ -70,27 +80,75 @@ async def create_conversation_message(request: web.Request):
7080 _ConversationMessageCreateBodyParams , request
7181 )
7282
73- user_primary_gid = await users_service .get_user_primary_group_id (
74- request .app , user_id = req_ctx .user_id
75- )
76- conversation = await _conversation_service .get_conversation_for_user (
77- app = request .app ,
78- conversation_id = path_params .conversation_id ,
79- user_group_id = user_primary_gid ,
83+ _conversation = await _conversation_service .get_conversation (
84+ request .app , conversation_id = path_params .conversation_id
8085 )
81- # Ensure only support conversations are allowed
82- if conversation .type != ConversationType .SUPPORT :
83- raise_unsupported_type (conversation .type )
86+ if _conversation .type != ConversationType .SUPPORT :
87+ raise_unsupported_type (_conversation .type )
8488
85- message = await _conversation_message_service .create_message (
89+ # This function takes care of granting support user access to the message
90+ await _conversation_service .get_support_conversation_for_user (
8691 app = request .app ,
8792 user_id = req_ctx .user_id ,
88- project_id = None , # Support conversations don't use project_id
93+ product_name = req_ctx . product_name ,
8994 conversation_id = path_params .conversation_id ,
90- content = body_params .content ,
91- type_ = body_params .type ,
9295 )
9396
97+ message , is_first_message = (
98+ await _conversation_message_service .create_support_message_with_first_check (
99+ app = request .app ,
100+ user_id = req_ctx .user_id ,
101+ project_id = None , # Support conversations don't use project_id
102+ conversation_id = path_params .conversation_id ,
103+ content = body_params .content ,
104+ type_ = body_params .type ,
105+ )
106+ )
107+
108+ # NOTE: This is done here in the Controller layer, as the interface around email currently needs request
109+ if is_first_message :
110+ try :
111+ user = await users_service .get_user (request .app , req_ctx .user_id )
112+ product = products_web .get_current_product (request )
113+ template_name = "request_support.jinja2"
114+ destination_email = product .support_email
115+ email_template_path = await products_web .get_product_template_path (
116+ request , template_name
117+ )
118+ _url = request .url
119+ if _url .port :
120+ _conversation_url = f"{ _url .scheme } ://{ _url .host } :{ _url .port } /#/conversations/{ path_params .conversation_id } "
121+ else :
122+ _conversation_url = f"{ _url .scheme } ://{ _url .host } /#/conversations/{ path_params .conversation_id } "
123+ _extra_context = _conversation .extra_context
124+ await email_service .send_email_from_template (
125+ request ,
126+ from_ = product .support_email ,
127+ to = destination_email ,
128+ template = email_template_path ,
129+ context = {
130+ "host" : request .host ,
131+ "product" : product .model_dump (
132+ include = {
133+ "display_name" ,
134+ }
135+ ),
136+ "first_name" : user ["first_name" ],
137+ "last_name" : user ["last_name" ],
138+ "user_email" : user ["email" ],
139+ "conversation_url" : _conversation_url ,
140+ "message_content" : message .content ,
141+ "extra_context" : _extra_context ,
142+ "dumps" : functools .partial (_json_encoder_and_dumps , indent = 1 ),
143+ },
144+ )
145+ except Exception : # pylint: disable=broad-except
146+ _logger .exception (
147+ "Failed to send '%s' email to %s (this means the FogBugz case for the request was not created)." ,
148+ template_name ,
149+ destination_email ,
150+ )
151+
94152 data = ConversationMessageRestGet .from_domain_model (message )
95153 return envelope_json_response (data , web .HTTPCreated )
96154
@@ -109,16 +167,19 @@ async def list_conversation_messages(request: web.Request):
109167 _ListConversationMessageQueryParams , request
110168 )
111169
112- user_primary_gid = await users_service . get_user_primary_group_id (
113- request .app , user_id = req_ctx . user_id
170+ _conversation = await _conversation_service . get_conversation (
171+ request .app , conversation_id = path_params . conversation_id
114172 )
115- conversation = await _conversation_service .get_conversation_for_user (
173+ if _conversation .type != ConversationType .SUPPORT :
174+ raise_unsupported_type (_conversation .type )
175+
176+ # This function takes care of granting support user access to the message
177+ await _conversation_service .get_support_conversation_for_user (
116178 app = request .app ,
179+ user_id = req_ctx .user_id ,
180+ product_name = req_ctx .product_name ,
117181 conversation_id = path_params .conversation_id ,
118- user_group_id = user_primary_gid ,
119182 )
120- if conversation .type != ConversationType .SUPPORT :
121- raise_unsupported_type (conversation .type )
122183
123184 total , messages = (
124185 await _conversation_message_service .list_messages_for_conversation (
@@ -160,16 +221,19 @@ async def get_conversation_message(request: web.Request):
160221 _ConversationMessagePathParams , request
161222 )
162223
163- user_primary_gid = await users_service . get_user_primary_group_id (
164- request .app , user_id = req_ctx . user_id
224+ _conversation = await _conversation_service . get_conversation (
225+ request .app , conversation_id = path_params . conversation_id
165226 )
166- conversation = await _conversation_service .get_conversation_for_user (
227+ if _conversation .type != ConversationType .SUPPORT :
228+ raise_unsupported_type (_conversation .type )
229+
230+ # This function takes care of granting support user access to the message
231+ await _conversation_service .get_support_conversation_for_user (
167232 app = request .app ,
233+ user_id = req_ctx .user_id ,
234+ product_name = req_ctx .product_name ,
168235 conversation_id = path_params .conversation_id ,
169- user_group_id = user_primary_gid ,
170236 )
171- if conversation .type != ConversationType .SUPPORT :
172- raise_unsupported_type (conversation .type )
173237
174238 message = await _conversation_message_service .get_message (
175239 app = request .app ,
@@ -195,16 +259,19 @@ async def update_conversation_message(request: web.Request):
195259 )
196260 body_params = await parse_request_body_as (ConversationMessagePatch , request )
197261
198- user_primary_gid = await users_service . get_user_primary_group_id (
199- request .app , user_id = req_ctx . user_id
262+ _conversation = await _conversation_service . get_conversation (
263+ request .app , conversation_id = path_params . conversation_id
200264 )
201- conversation = await _conversation_service .get_conversation_for_user (
265+ if _conversation .type != ConversationType .SUPPORT :
266+ raise_unsupported_type (_conversation .type )
267+
268+ # This function takes care of granting support user access to the message
269+ await _conversation_service .get_support_conversation_for_user (
202270 app = request .app ,
271+ user_id = req_ctx .user_id ,
272+ product_name = req_ctx .product_name ,
203273 conversation_id = path_params .conversation_id ,
204- user_group_id = user_primary_gid ,
205274 )
206- if conversation .type != ConversationType .SUPPORT :
207- raise_unsupported_type (conversation .type )
208275
209276 message = await _conversation_message_service .update_message (
210277 app = request .app ,
@@ -231,16 +298,19 @@ async def delete_conversation_message(request: web.Request):
231298 _ConversationMessagePathParams , request
232299 )
233300
234- user_primary_gid = await users_service . get_user_primary_group_id (
235- request .app , user_id = req_ctx . user_id
301+ _conversation = await _conversation_service . get_conversation (
302+ request .app , conversation_id = path_params . conversation_id
236303 )
237- conversation = await _conversation_service .get_conversation_for_user (
304+ if _conversation .type != ConversationType .SUPPORT :
305+ raise_unsupported_type (_conversation .type )
306+
307+ # This function takes care of granting support user access to the message
308+ await _conversation_service .get_support_conversation_for_user (
238309 app = request .app ,
310+ user_id = req_ctx .user_id ,
311+ product_name = req_ctx .product_name ,
239312 conversation_id = path_params .conversation_id ,
240- user_group_id = user_primary_gid ,
241313 )
242- if conversation .type != ConversationType .SUPPORT :
243- raise_unsupported_type (conversation .type )
244314
245315 await _conversation_message_service .delete_message (
246316 app = request .app ,
0 commit comments