11use crate :: db:: repr:: { ApiKey , Permission } ;
22use crate :: prelude:: api:: * ;
3- use crate :: types:: BotState ;
3+ use crate :: prelude:: db:: * ;
4+ use crate :: prelude:: i18n:: * ;
5+ use crate :: prelude:: utils:: * ;
6+ use crate :: types:: { BotCommand , BotState } ;
47use axum:: Json ;
58use axum:: extract:: { Extension , State } ;
69use axum:: http:: StatusCode ;
710use rustmail_types:: CreateTicket ;
11+ use serenity:: all:: { ChannelId , CreateChannel , GuildId , UserId } ;
12+ use std:: collections:: HashMap ;
813use std:: sync:: Arc ;
914use tokio:: sync:: Mutex ;
1015
@@ -16,26 +21,214 @@ pub async fn handle_external_ticket_create(
1621 check_permission ( & api_key, Permission :: CreateTicket )
1722 . map_err ( |e| ( StatusCode :: FORBIDDEN , format ! ( "{:?}" , e) ) ) ?;
1823
19- let _current_config = {
24+ let user_id_u64 = update. discord_id . parse :: < u64 > ( ) . map_err ( |_| {
25+ (
26+ StatusCode :: BAD_REQUEST ,
27+ "Invalid Discord ID format" . to_string ( ) ,
28+ )
29+ } ) ?;
30+
31+ let user_id = UserId :: new ( user_id_u64) ;
32+
33+ let ( mut config, db_pool, bot_http, command_tx) = {
2034 let state = bot_state. lock ( ) . await ;
21- match & state. config {
22- Some ( c) => c. clone ( ) ,
23- None => {
24- return Err ( (
25- StatusCode :: INTERNAL_SERVER_ERROR ,
26- "Configuration not loaded" . to_string ( ) ,
27- ) ) ;
28- }
29- }
35+ let config = state
36+ . config
37+ . as_ref ( )
38+ . ok_or ( (
39+ StatusCode :: INTERNAL_SERVER_ERROR ,
40+ "Configuration not loaded" . to_string ( ) ,
41+ ) ) ?
42+ . clone ( ) ;
43+ let db_pool = state
44+ . db_pool
45+ . as_ref ( )
46+ . ok_or ( (
47+ StatusCode :: INTERNAL_SERVER_ERROR ,
48+ "Database not available" . to_string ( ) ,
49+ ) ) ?
50+ . clone ( ) ;
51+ let bot_http = state
52+ . bot_http
53+ . as_ref ( )
54+ . ok_or ( (
55+ StatusCode :: INTERNAL_SERVER_ERROR ,
56+ "Bot HTTP client not available" . to_string ( ) ,
57+ ) ) ?
58+ . clone ( ) ;
59+ let command_tx = state. command_tx . clone ( ) ;
60+
61+ ( config, db_pool, bot_http, command_tx)
3062 } ;
3163
64+ config. db_pool = Some ( db_pool. clone ( ) ) ;
65+
66+ println ! (
67+ "API Key #{} creating ticket for Discord ID: {}" ,
68+ api_key. id, user_id_u64
69+ ) ;
70+
71+ let user = bot_http. get_user ( user_id) . await . map_err ( |e| {
72+ (
73+ StatusCode :: NOT_FOUND ,
74+ format ! ( "Discord user not found: {}" , e) ,
75+ )
76+ } ) ?;
77+
78+ if user. bot {
79+ return Err ( (
80+ StatusCode :: BAD_REQUEST ,
81+ "Cannot create ticket for bot users" . to_string ( ) ,
82+ ) ) ;
83+ }
84+
85+ let ( tx, rx) = tokio:: sync:: oneshot:: channel ( ) ;
86+ command_tx
87+ . send ( BotCommand :: CheckUserIsMember {
88+ user_id : user_id_u64,
89+ resp : tx,
90+ } )
91+ . await
92+ . map_err ( |_| {
93+ (
94+ StatusCode :: INTERNAL_SERVER_ERROR ,
95+ "Failed to communicate with bot" . to_string ( ) ,
96+ )
97+ } ) ?;
98+
99+ let is_member = rx. await . map_err ( |_| {
100+ (
101+ StatusCode :: INTERNAL_SERVER_ERROR ,
102+ "Failed to check guild membership" . to_string ( ) ,
103+ )
104+ } ) ?;
105+
106+ if !is_member {
107+ return Err ( (
108+ StatusCode :: FORBIDDEN ,
109+ "User is not a member of the community guild" . to_string ( ) ,
110+ ) ) ;
111+ }
112+
113+ if thread_exists ( user_id, & db_pool) . await {
114+ return if let Some ( channel_id_str) = get_thread_channel_by_user_id ( user_id, & db_pool) . await
115+ {
116+ Err ( (
117+ StatusCode :: CONFLICT ,
118+ format ! ( "User already has an active ticket: <#{}>" , channel_id_str) ,
119+ ) )
120+ } else {
121+ Err ( (
122+ StatusCode :: CONFLICT ,
123+ "User already has an active ticket" . to_string ( ) ,
124+ ) )
125+ } ;
126+ }
127+
128+ let username = user. name . clone ( ) ;
129+ let thread_name = format ! ( "🔴・{}・0m" , username) ;
130+ let staff_guild_id = GuildId :: new ( config. bot . get_staff_guild_id ( ) ) ;
131+ let inbox_category_id = ChannelId :: new ( config. thread . inbox_category_id ) ;
132+
133+ let channel_builder = CreateChannel :: new ( & thread_name) . category ( inbox_category_id) ;
134+
135+ let channel = staff_guild_id
136+ . create_channel ( & bot_http, channel_builder)
137+ . await
138+ . map_err ( |e| {
139+ (
140+ StatusCode :: INTERNAL_SERVER_ERROR ,
141+ format ! ( "Failed to create Discord channel: {}" , e) ,
142+ )
143+ } ) ?;
144+
145+ create_thread_for_user ( & channel, user_id_u64 as i64 , & username, & db_pool)
146+ . await
147+ . map_err ( |e| {
148+ let http_clone = bot_http. clone ( ) ;
149+ let channel_id = channel. id ;
150+ tokio:: spawn ( async move {
151+ let _ = http_clone. delete_channel ( channel_id, None ) . await ;
152+ } ) ;
153+ (
154+ StatusCode :: INTERNAL_SERVER_ERROR ,
155+ format ! ( "Failed to create thread record: {}" , e) ,
156+ )
157+ } ) ?;
158+
159+ let community_guild_id = GuildId :: new ( config. bot . get_community_guild_id ( ) ) ;
160+ let member_join_date = community_guild_id
161+ . member ( & bot_http, user_id)
162+ . await
163+ . ok ( )
164+ . and_then ( |m| m. joined_at )
165+ . map ( |dt| dt. format ( "%Y-%m-%d" ) . to_string ( ) )
166+ . unwrap_or_else ( || "Unknown" . to_string ( ) ) ;
167+
168+ let logs_count = match get_logs_from_user_id ( & user_id. to_string ( ) , & db_pool) . await {
169+ Ok ( logs) => logs. len ( ) ,
170+ Err ( _) => 0 ,
171+ } ;
172+
173+ let params = {
174+ let mut p = HashMap :: new ( ) ;
175+ p. insert ( "logs_count" . to_string ( ) , logs_count. to_string ( ) ) ;
176+ p. insert ( "prefix" . to_string ( ) , config. command . prefix . clone ( ) ) ;
177+ p
178+ } ;
179+
180+ let logs_info = get_translated_message (
181+ & config,
182+ "new_thread.show_logs" ,
183+ Some ( & params) ,
184+ None ,
185+ None ,
186+ None ,
187+ )
188+ . await ;
189+
190+ let open_thread_message = get_user_recap ( user_id, & username, & member_join_date, & logs_info) ;
191+
192+ let ctx = {
193+ let state = bot_state. lock ( ) . await ;
194+ let ctx_lock = state. bot_context . read ( ) . await ;
195+ ctx_lock
196+ . as_ref ( )
197+ . ok_or ( (
198+ StatusCode :: INTERNAL_SERVER_ERROR ,
199+ "Bot context not available" . to_string ( ) ,
200+ ) ) ?
201+ . clone ( )
202+ } ;
203+
204+ if let Err ( e) = MessageBuilder :: system_message ( & ctx, & config)
205+ . to_channel ( channel. id )
206+ . content ( open_thread_message)
207+ . send ( true )
208+ . await
209+ {
210+ eprintln ! ( "Failed to send message to channel via MessageBuilder: {:?}" , e) ;
211+ }
212+
213+ if let Err ( e) = MessageBuilder :: system_message ( & ctx, & config)
214+ . content ( & config. bot . welcome_message )
215+ . to_user ( user_id)
216+ . send ( true )
217+ . await
218+ {
219+ eprintln ! ( "Failed to send DM via MessageBuilder: {:?}" , e) ;
220+ }
221+
32222 println ! (
33- "API Key #{} creating ticket for Discord ID : {:?} " ,
34- api_key. id, update . discord_id
223+ "API Key #{} successfully created ticket for user {} (channel : {}) " ,
224+ api_key. id, username , channel . id
35225 ) ;
36226
37227 Ok ( Json ( serde_json:: json!( {
38- "status" : "ticket created" ,
39- "message" : "Ticket creation endpoint - implementation pending"
228+ "success" : true ,
229+ "channel_id" : channel. id. to_string( ) ,
230+ "user_id" : user_id_u64. to_string( ) ,
231+ "username" : username,
232+ "message" : "Ticket created successfully"
40233 } ) ) )
41234}
0 commit comments