@@ -25,17 +25,21 @@ use crate::{
2525 message_app:: CreateMessageApp ,
2626 permissions,
2727 types:: {
28- EndpointId , EventChannel , EventChannelSet , EventTypeName , EventTypeNameSet ,
29- MessageAttemptTriggerType , MessageId , MessageUid ,
28+ ApplicationIdOrUid , EndpointId , EventChannel , EventChannelSet , EventTypeName ,
29+ EventTypeNameSet , MessageAttemptTriggerType , MessageId , MessageUid , OrganizationId ,
3030 } ,
3131 } ,
3232 db:: models:: { application, message, messagecontent} ,
33- error:: { http_error_on_conflict, Error , HttpError , Result } ,
33+ error:: { http_error_on_conflict, Error , HttpError , Result , ValidationErrorItem } ,
3434 queue:: { MessageTaskBatch , TaskQueueProducer } ,
35- v1:: utils:: {
36- filter_and_paginate_time_limited, openapi_tag, validation_error, ApplicationMsgPath ,
37- EventTypesQueryParams , JsonStatus , ListResponse , ModelIn , ModelOut , PaginationDescending ,
38- PaginationLimit , ReversibleIterator , ValidatedJson , ValidatedQuery ,
35+ v1:: {
36+ endpoints:: application:: { create_app_from_app_in, ApplicationIn } ,
37+ utils:: {
38+ filter_and_paginate_time_limited, openapi_tag, validation_error, validation_errors,
39+ ApplicationMsgPath , ApplicationPath , EventTypesQueryParams , JsonStatus , ListResponse ,
40+ ModelIn , ModelOut , PaginationDescending , PaginationLimit , ReversibleIterator ,
41+ ValidatedJson , ValidatedQuery ,
42+ } ,
3943 } ,
4044 AppState ,
4145} ;
@@ -127,6 +131,14 @@ pub struct MessageIn {
127131 #[ serde( rename = "transformationsParams" ) ]
128132 #[ schemars( skip) ]
129133 pub extra_params : Option < MessageInExtraParams > ,
134+
135+ /// Optionally creates a new application alongside the message.
136+ ///
137+ /// If the application id or uid that is used in the path already exists,
138+ /// this argument is ignored.
139+ #[ validate]
140+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
141+ pub application : Option < ApplicationIn > ,
130142}
131143
132144impl MessageIn {
@@ -335,23 +347,64 @@ async fn create_message(
335347 ValidatedQuery ( CreateMessageQueryParams { with_content } ) : ValidatedQuery <
336348 CreateMessageQueryParams ,
337349 > ,
338- permissions:: OrganizationWithApplication { app } : permissions:: OrganizationWithApplication ,
350+ Path ( ApplicationPath { app_id } ) : Path < ApplicationPath > ,
351+ permissions:: Organization { org_id } : permissions:: Organization ,
339352 ValidatedJson ( data) : ValidatedJson < MessageIn > ,
340353) -> Result < JsonStatus < 202 , MessageOut > > {
341354 Ok ( JsonStatus (
342- create_message_inner ( db, queue_tx, cache, with_content, None , data, app) . await ?,
355+ create_message_inner (
356+ db,
357+ queue_tx,
358+ cache,
359+ with_content,
360+ None ,
361+ data,
362+ org_id,
363+ app_id,
364+ )
365+ . await ?,
343366 ) )
344367}
345368
369+ #[ allow( clippy:: too_many_arguments) ]
346370pub ( crate ) async fn create_message_inner (
347371 db : & DatabaseConnection ,
348372 queue_tx : TaskQueueProducer ,
349373 cache : Cache ,
350374 with_content : bool ,
351375 force_endpoint : Option < EndpointId > ,
352376 data : MessageIn ,
353- app : application:: Model ,
377+ org_id : OrganizationId ,
378+ app_id : ApplicationIdOrUid ,
354379) -> Result < MessageOut > {
380+ app_id. validate ( ) . map_err ( |e| {
381+ HttpError :: unprocessable_entity ( validation_errors (
382+ vec ! [ "path" . to_owned( ) , "app_id_or_uid" . to_owned( ) ] ,
383+ e,
384+ ) )
385+ } ) ?;
386+
387+ let app_from_path_app_id =
388+ application:: Entity :: secure_find_by_id_or_uid ( org_id. clone ( ) , app_id. to_owned ( ) )
389+ . one ( db)
390+ . await ?;
391+
392+ let app = match ( & data. application , app_from_path_app_id) {
393+ ( None , None ) => {
394+ return Err (
395+ HttpError :: not_found ( None , Some ( "Application not found" . to_string ( ) ) ) . into ( ) ,
396+ ) ;
397+ }
398+
399+ ( _, Some ( app_from_path_param) ) => app_from_path_param,
400+ ( Some ( cmg_app) , None ) => {
401+ validate_create_app_uid ( & app_id, cmg_app) ?;
402+ let ( app, _metadata) = create_app_from_app_in ( db, cmg_app. to_owned ( ) , org_id) . await ?;
403+
404+ app
405+ }
406+ } ;
407+
355408 let create_message_app = CreateMessageApp :: layered_fetch (
356409 & cache,
357410 db,
@@ -407,6 +460,37 @@ pub(crate) async fn create_message_inner(
407460 Ok ( msg_out)
408461}
409462
463+ fn validate_create_app_uid ( app_id_or_uid : & ApplicationIdOrUid , data : & ApplicationIn ) -> Result < ( ) > {
464+ // If implicit app creation is requested then the UID must be set
465+ // in the request body, and it must match the UID given in the path
466+ if let Some ( uid) = & data. uid {
467+ if uid. 0 != app_id_or_uid. 0 {
468+ return Err ( HttpError :: unprocessable_entity ( vec ! [ ValidationErrorItem {
469+ loc: vec![
470+ "body" . to_string( ) ,
471+ "application" . to_string( ) ,
472+ "uid" . to_string( ) ,
473+ ] ,
474+ msg: "Application UID in the path and body must match" . to_string( ) ,
475+ ty: "application_uid_mismatch" . to_string( ) ,
476+ } ] )
477+ . into ( ) ) ;
478+ }
479+ } else {
480+ return Err ( HttpError :: unprocessable_entity ( vec ! [ ValidationErrorItem {
481+ loc: vec![
482+ "body" . to_string( ) ,
483+ "application" . to_string( ) ,
484+ "uid" . to_string( ) ,
485+ ] ,
486+ msg: "Application UID not set in body" . to_string( ) ,
487+ ty: "application_uid_missing" . to_string( ) ,
488+ } ] )
489+ . into ( ) ) ;
490+ }
491+ Ok ( ( ) )
492+ }
493+
410494#[ derive( Debug , Deserialize , Validate , JsonSchema ) ]
411495pub struct GetMessageQueryParams {
412496 /// When `true` message payloads are included in the response
0 commit comments