partybooking_controller.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. // Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
  2. // For more information, see LICENCE in the main folder
  3. #include "partybooking_controller.hpp"
  4. #include <string>
  5. #include <common/showmsg.hpp>
  6. #include <common/sql.hpp>
  7. #include <common/strlib.hpp>
  8. #include "http.hpp"
  9. #include "auth.hpp"
  10. #include "sqllock.hpp"
  11. #include "web.hpp"
  12. const size_t WORLD_NAME_LENGTH = 32;
  13. const size_t COMMENT_LENGTH = 255;
  14. enum e_booking_purpose : uint16{
  15. BOOKING_PURPOSE_ALL = 0,
  16. BOOKING_PURPOSE_QUEST,
  17. BOOKING_PURPOSE_FIELD,
  18. BOOKING_PURPOSE_DUNGEON,
  19. BOOKING_PURPOSE_MD,
  20. BOOKING_PURPOSE_PARADISE,
  21. BOOKING_PURPOSE_OTHER,
  22. BOOKING_PURPOSE_MAX
  23. };
  24. struct s_party_booking_entry{
  25. uint32 account_id;
  26. uint32 char_id;
  27. std::string char_name;
  28. uint16 purpose;
  29. bool assist;
  30. bool damagedealer;
  31. bool healer;
  32. bool tanker;
  33. uint16 minimum_level;
  34. uint16 maximum_level;
  35. std::string comment;
  36. public:
  37. std::string to_json( std::string& world_name );
  38. };
  39. std::string s_party_booking_entry::to_json( std::string& world_name ){
  40. return
  41. "{ \"AID\": " + std::to_string( this->account_id ) +
  42. ", \"GID\": " + std::to_string( this->char_id ) +
  43. ", \"CharName\": \"" + this->char_name + "\""
  44. ", \"WorldName\": \"" + world_name + "\""
  45. ", \"Tanker\": " + ( this->tanker ? "1": "0" ) +
  46. ", \"Healer\": " + ( this->healer ? "1": "0" ) +
  47. ", \"Dealer\": " + ( this->damagedealer ? "1" : "0" ) +
  48. ", \"Assist\": " + ( this->assist ? "1" : "0" ) +
  49. ", \"MinLV\": " + std::to_string( this->minimum_level ) +
  50. ", \"MaxLV\": " + std::to_string( this->maximum_level ) +
  51. ", \"Memo\": \"" + this->comment + "\""
  52. ", \"Type\": " + std::to_string( this->purpose ) +
  53. "}";
  54. }
  55. bool party_booking_read( std::string& world_name, std::vector<s_party_booking_entry>& output, const std::string& condition, const std::string& order ){
  56. SQLLock sl(MAP_SQL_LOCK);
  57. sl.lock();
  58. auto handle = sl.getHandle();
  59. SqlStmt* stmt = SqlStmt_Malloc( handle );
  60. s_party_booking_entry entry;
  61. char world_name_escaped[WORLD_NAME_LENGTH * 2 + 1];
  62. char char_name[NAME_LENGTH ];
  63. char comment[COMMENT_LENGTH + 1];
  64. Sql_EscapeString( nullptr, world_name_escaped, world_name.c_str() );
  65. std::string query = "SELECT `account_id`, `char_id`, `char_name`, `purpose`, `assist`, `damagedealer`, `healer`, `tanker`, `minimum_level`, `maximum_level`, `comment` FROM `" + std::string( partybookings_table ) + "` WHERE `world_name` = ? AND " + condition + order;
  66. if( SQL_SUCCESS != SqlStmt_Prepare( stmt, query.c_str() )
  67. || SQL_SUCCESS != SqlStmt_BindParam( stmt, 0, SQLDT_STRING, (void*)world_name_escaped, strlen( world_name_escaped ) )
  68. || SQL_SUCCESS != SqlStmt_Execute( stmt )
  69. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 0, SQLDT_UINT32, &entry.account_id, 0, NULL, NULL )
  70. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 1, SQLDT_UINT32, &entry.char_id, 0, NULL, NULL )
  71. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 2, SQLDT_STRING, (void*)char_name, sizeof( char_name ), NULL, NULL )
  72. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 3, SQLDT_UINT16, &entry.purpose, 0, NULL, NULL )
  73. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 4, SQLDT_UINT8, &entry.assist, 0, NULL, NULL )
  74. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 5, SQLDT_UINT8, &entry.damagedealer, 0, NULL, NULL )
  75. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 6, SQLDT_UINT8, &entry.healer, 0, NULL, NULL )
  76. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 7, SQLDT_UINT8, &entry.tanker, 0, NULL, NULL )
  77. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 8, SQLDT_UINT16, &entry.minimum_level, 0, NULL, NULL )
  78. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 9, SQLDT_UINT16, &entry.maximum_level, 0, NULL, NULL )
  79. || SQL_SUCCESS != SqlStmt_BindColumn( stmt, 10, SQLDT_STRING, (void*)comment, sizeof( comment ), NULL, NULL )
  80. ){
  81. SqlStmt_ShowDebug( stmt );
  82. SqlStmt_Free( stmt );
  83. sl.unlock();
  84. return false;
  85. }
  86. while( SQL_SUCCESS == SqlStmt_NextRow( stmt ) ){
  87. entry.char_name = char_name;
  88. entry.comment = comment;
  89. output.push_back( entry );
  90. }
  91. SqlStmt_Free( stmt );
  92. sl.unlock();
  93. return true;
  94. }
  95. HANDLER_FUNC(partybooking_add){
  96. if( !isAuthorized( req, false ) ){
  97. res.status = HTTP_BAD_REQUEST;
  98. res.set_content( "Error", "text/plain" );
  99. return;
  100. }
  101. uint32 aid = std::stoi( req.get_file_value( "AID" ).content );
  102. uint32 cid = std::stoi( req.get_file_value( "GID" ).content );
  103. SQLLock csl( CHAR_SQL_LOCK );
  104. csl.lock();
  105. auto chandle = csl.getHandle();
  106. SqlStmt* stmt = SqlStmt_Malloc( chandle );
  107. if( SQL_SUCCESS != SqlStmt_Prepare( stmt, "SELECT 1 FROM `%s` WHERE `leader_id` = ? AND `leader_char` = ?", party_table, aid, cid )
  108. || SQL_SUCCESS != SqlStmt_BindParam( stmt, 0, SQLDT_UINT32, &aid, sizeof( aid ) )
  109. || SQL_SUCCESS != SqlStmt_BindParam( stmt, 1, SQLDT_UINT32, &cid, sizeof( cid ) )
  110. || SQL_SUCCESS != SqlStmt_Execute( stmt )
  111. ){
  112. SqlStmt_ShowDebug( stmt );
  113. SqlStmt_Free( stmt );
  114. csl.unlock();
  115. res.status = HTTP_BAD_REQUEST;
  116. res.set_content( "Error", "text/plain" );
  117. return;
  118. }
  119. if( SqlStmt_NumRows( stmt ) <= 0 ){
  120. // No party or not party leader
  121. SqlStmt_Free( stmt );
  122. csl.unlock();
  123. res.status = HTTP_BAD_REQUEST;
  124. res.set_content( "Error", "text/plain" );
  125. return;
  126. }
  127. SqlStmt_Free( stmt );
  128. csl.unlock();
  129. auto world_name = req.get_file_value( "WorldName" ).content;
  130. if( world_name.length() > WORLD_NAME_LENGTH ){
  131. res.status = HTTP_BAD_REQUEST;
  132. res.set_content( "Error", "text/plain" );
  133. return;
  134. }
  135. s_party_booking_entry entry = {};
  136. entry.account_id = aid;
  137. entry.char_id = cid;
  138. entry.char_name = req.get_file_value( "CharName" ).content;
  139. entry.purpose = std::stoi( req.get_file_value( "Type" ).content );
  140. entry.assist = std::stoi( req.get_file_value( "Assist" ).content ) != 0;
  141. entry.damagedealer = std::stoi( req.get_file_value( "Dealer" ).content ) != 0;
  142. entry.healer = std::stoi( req.get_file_value( "Healer" ).content ) != 0;
  143. entry.tanker = std::stoi( req.get_file_value( "Tanker" ).content ) != 0;
  144. entry.minimum_level = std::stoi( req.get_file_value( "MinLV" ).content );
  145. entry.maximum_level = std::stoi( req.get_file_value( "MaxLV" ).content );
  146. entry.comment = req.get_file_value( "Memo" ).content;
  147. if( entry.char_name.length() > NAME_LENGTH ){
  148. res.status = HTTP_BAD_REQUEST;
  149. res.set_content( "Error", "text/plain" );
  150. return;
  151. }
  152. if( entry.purpose >= BOOKING_PURPOSE_MAX ){
  153. res.status = HTTP_BAD_REQUEST;
  154. res.set_content( "Error", "text/plain" );
  155. return;
  156. }
  157. if( entry.comment.length() > COMMENT_LENGTH ){
  158. res.status = HTTP_BAD_REQUEST;
  159. res.set_content( "Error", "text/plain" );
  160. return;
  161. }
  162. char world_name_escaped[WORLD_NAME_LENGTH * 2 + 1];
  163. char char_name_escaped[NAME_LENGTH * 2 + 1];
  164. char comment_escaped[COMMENT_LENGTH * 2 + 1];
  165. Sql_EscapeString( nullptr, world_name_escaped, world_name.c_str() );
  166. Sql_EscapeString( nullptr, char_name_escaped, entry.char_name.c_str() );
  167. Sql_EscapeString( nullptr, comment_escaped, entry.comment.c_str() );
  168. StringBuf buf;
  169. StringBuf_Init( &buf );
  170. StringBuf_Printf( &buf, "REPLACE INTO `%s` ( `world_name`, `account_id`, `char_id`, `char_name`, `purpose`, `assist`, `damagedealer`, `healer`, `tanker`, `minimum_level`, `maximum_level`, `comment` ) VALUES ( ", partybookings_table );
  171. StringBuf_Printf( &buf, "'%s',", world_name_escaped );
  172. StringBuf_Printf( &buf, "'%u',", entry.account_id );
  173. StringBuf_Printf( &buf, "'%u',", entry.char_id );
  174. StringBuf_Printf( &buf, "'%s',", char_name_escaped );
  175. StringBuf_Printf( &buf, "'%hu',", entry.purpose );
  176. StringBuf_Printf( &buf, "'%d',", entry.assist );
  177. StringBuf_Printf( &buf, "'%d',", entry.damagedealer );
  178. StringBuf_Printf( &buf, "'%d',", entry.healer );
  179. StringBuf_Printf( &buf, "'%d',", entry.tanker );
  180. StringBuf_Printf( &buf, "'%hu',", entry.minimum_level );
  181. StringBuf_Printf( &buf, "'%hu',", entry.maximum_level );
  182. StringBuf_Printf( &buf, "'%s' );", comment_escaped );
  183. SQLLock msl( MAP_SQL_LOCK );
  184. msl.lock();
  185. auto mhandle = msl.getHandle();
  186. if( SQL_ERROR == Sql_QueryStr( mhandle, StringBuf_Value( &buf ) ) ){
  187. Sql_ShowDebug( mhandle );
  188. StringBuf_Destroy( &buf );
  189. msl.unlock();
  190. res.status = HTTP_BAD_REQUEST;
  191. res.set_content( "Error", "text/plain" );
  192. return;
  193. }
  194. StringBuf_Destroy( &buf );
  195. msl.unlock();
  196. res.set_content( "{ \"Type\": 1 }", "application/json" );
  197. }
  198. HANDLER_FUNC(partybooking_delete){
  199. if( !isAuthorized( req, false ) ){
  200. res.status = HTTP_BAD_REQUEST;
  201. res.set_content( "Error", "text/plain" );
  202. return;
  203. }
  204. auto world_name = req.get_file_value( "WorldName" ).content;
  205. auto account_id = std::stoi( req.get_file_value( "AID" ).content );
  206. if( world_name.length() > WORLD_NAME_LENGTH ){
  207. res.status = HTTP_BAD_REQUEST;
  208. res.set_content( "Error", "text/plain" );
  209. return;
  210. }
  211. SQLLock sl( MAP_SQL_LOCK );
  212. sl.lock();
  213. auto handle = sl.getHandle();
  214. if( SQL_ERROR == Sql_Query( handle, "DELETE FROM `%s` WHERE `world_name` = '%s' AND `account_id` = '%d'", partybookings_table, world_name.c_str(), account_id ) ){
  215. Sql_ShowDebug( handle );
  216. sl.unlock();
  217. res.status = HTTP_BAD_REQUEST;
  218. res.set_content( "Error", "text/plain" );
  219. return;
  220. }
  221. sl.unlock();
  222. res.set_content( "{ \"Type\": 1 }", "application/json" );
  223. }
  224. HANDLER_FUNC(partybooking_get){
  225. if( !isAuthorized( req, false ) ){
  226. res.status = HTTP_BAD_REQUEST;
  227. res.set_content( "Error", "text/plain" );
  228. return;
  229. }
  230. auto world_name = req.get_file_value( "WorldName" ).content;
  231. auto account_id = std::stoi( req.get_file_value( "AID" ).content );
  232. if( world_name.length() > WORLD_NAME_LENGTH ){
  233. res.status = HTTP_BAD_REQUEST;
  234. res.set_content( "Error", "text/plain" );
  235. return;
  236. }
  237. std::vector<s_party_booking_entry> bookings;
  238. if( !party_booking_read( world_name, bookings, "`account_id` = '" + std::to_string( account_id ) + "'", "" ) ){
  239. res.status = HTTP_BAD_REQUEST;
  240. res.set_content( "Error", "text/plain" );
  241. return;
  242. }
  243. std::string response;
  244. if( bookings.empty() ){
  245. response = "{ \"Type\": 1 }";
  246. }else{
  247. response = "{ \"Type\": 1, data: " + bookings.at( 0 ).to_json( world_name ) + " }";
  248. }
  249. res.set_content( response, "application/json" );
  250. }
  251. HANDLER_FUNC(partybooking_info){
  252. if( !isAuthorized( req, false ) ){
  253. res.status = HTTP_BAD_REQUEST;
  254. res.set_content( "Error", "text/plain" );
  255. return;
  256. }
  257. auto world_name = req.get_file_value( "WorldName" ).content;
  258. auto account_id = std::stoi( req.get_file_value( "QueryAID" ).content );
  259. if( world_name.length() > WORLD_NAME_LENGTH ){
  260. res.status = HTTP_BAD_REQUEST;
  261. res.set_content( "Error", "text/plain" );
  262. return;
  263. }
  264. std::vector<s_party_booking_entry> bookings;
  265. if( !party_booking_read( world_name, bookings, "`account_id` = '" + std::to_string( account_id ) + "'", "" ) ){
  266. res.status = HTTP_BAD_REQUEST;
  267. res.set_content( "Error", "text/plain" );
  268. return;
  269. }
  270. std::string response;
  271. if( bookings.empty() ){
  272. response = "{ \"Type\": 1 }";
  273. }else{
  274. response = "{ \"Type\": 1, \"data\": [" + bookings.at( 0 ).to_json( world_name ) + "] }";
  275. }
  276. res.set_content( response, "application/json" );
  277. }
  278. HANDLER_FUNC(partybooking_list){
  279. if( !isAuthorized( req, false ) ){
  280. res.status = HTTP_BAD_REQUEST;
  281. res.set_content( "Error", "text/plain" );
  282. return;
  283. }
  284. static const std::string condition( "1=1" );
  285. auto world_name = req.get_file_value( "WorldName" ).content;
  286. if( world_name.length() > WORLD_NAME_LENGTH ){
  287. res.status = HTTP_BAD_REQUEST;
  288. res.set_content( "Error", "text/plain" );
  289. return;
  290. }
  291. std::vector<s_party_booking_entry> bookings;
  292. if( !party_booking_read( world_name, bookings, condition, " ORDER BY `created` DESC" ) ){
  293. res.status = HTTP_BAD_REQUEST;
  294. res.set_content( "Error", "text/plain" );
  295. return;
  296. }
  297. std::string response;
  298. response = "{ \"Type\": 1, \"totalPage\": ";
  299. response += std::to_string( bookings.size() );
  300. response += ", \"data\": [";
  301. for( size_t i = 0, max = bookings.size(); i < max; i++ ){
  302. s_party_booking_entry& booking = bookings.at( i );
  303. response += booking.to_json( world_name );
  304. if( i < ( max - 1 ) ){
  305. response += ", ";
  306. }
  307. }
  308. response += "] }";
  309. res.set_content( response, "application/json" );
  310. }
  311. HANDLER_FUNC(partybooking_search){
  312. if( !isAuthorized( req, false ) ){
  313. res.status = HTTP_BAD_REQUEST;
  314. res.set_content( "Error", "text/plain" );
  315. return;
  316. }
  317. auto world_name = req.get_file_value( "WorldName" ).content;
  318. if( world_name.length() > WORLD_NAME_LENGTH ){
  319. res.status = HTTP_BAD_REQUEST;
  320. res.set_content( "Error", "text/plain" );
  321. return;
  322. }
  323. s_party_booking_entry entry;
  324. // Unconditional
  325. entry.minimum_level = std::stoi( req.get_file_value( "MinLV" ).content );
  326. entry.maximum_level = std::stoi( req.get_file_value( "MaxLV" ).content );
  327. // Conditional
  328. if( req.files.find( "Type" ) != req.files.end() ){
  329. entry.purpose = std::stoi( req.get_file_value( "Type" ).content );
  330. if( entry.purpose >= BOOKING_PURPOSE_MAX ){
  331. res.status = HTTP_BAD_REQUEST;
  332. res.set_content( "Error", "text/plain" );
  333. return;
  334. }
  335. }else{
  336. entry.purpose = BOOKING_PURPOSE_ALL;
  337. }
  338. if( req.files.find( "Assist" ) != req.files.end() ){
  339. entry.assist = std::stoi( req.get_file_value( "Assist" ).content ) != 0;
  340. }else{
  341. entry.assist = false;
  342. }
  343. if( req.files.find( "Dealer" ) != req.files.end() ){
  344. entry.damagedealer = std::stoi( req.get_file_value( "Dealer" ).content ) != 0;
  345. }else{
  346. entry.damagedealer = false;
  347. }
  348. if( req.files.find( "Healer" ) != req.files.end() ){
  349. entry.healer = std::stoi( req.get_file_value( "Healer" ).content ) != 0;
  350. }else{
  351. entry.healer = false;
  352. }
  353. if( req.files.find( "Tanker" ) != req.files.end() ){
  354. entry.tanker = std::stoi( req.get_file_value( "Tanker" ).content ) != 0;
  355. }else{
  356. entry.tanker = false;
  357. }
  358. if( req.files.find( "Memo" ) != req.files.end() ){
  359. entry.comment = req.get_file_value( "Memo" ).content;
  360. }else{
  361. entry.comment = "";
  362. }
  363. std::string condition;
  364. condition = "`minimum_level` = '" + std::to_string( entry.minimum_level ) + "'";
  365. condition += " AND `maximum_level` = '" + std::to_string( entry.maximum_level ) + "'";
  366. if( entry.purpose != BOOKING_PURPOSE_ALL ){
  367. condition += " AND `purpose` = '" + std::to_string( entry.purpose ) + "'";
  368. }
  369. if( entry.assist || entry.damagedealer || entry.healer || entry.tanker ){
  370. bool or_required = false;
  371. condition += "AND ( ";
  372. if( entry.assist ){
  373. if( or_required ){
  374. condition += " OR ";
  375. }else{
  376. or_required = true;
  377. }
  378. condition += "`assist` = '1'";
  379. }
  380. if( entry.damagedealer ){
  381. if( or_required ){
  382. condition += " OR ";
  383. }else{
  384. or_required = true;
  385. }
  386. condition += "`damagedealer` = '1'";
  387. }
  388. if( entry.healer ){
  389. if( or_required ){
  390. condition += " OR ";
  391. }else{
  392. or_required = true;
  393. }
  394. condition += "`healer` = '1'";
  395. }
  396. if( entry.tanker ){
  397. if( or_required ){
  398. condition += " OR ";
  399. }else{
  400. or_required = true;
  401. }
  402. condition += "`tanker` = '1'";
  403. }
  404. condition += " )";
  405. }
  406. if( !entry.comment.empty() ){
  407. if( entry.comment.length() > COMMENT_LENGTH ){
  408. res.status = HTTP_BAD_REQUEST;
  409. res.set_content( "Error", "text/plain" );
  410. return;
  411. }
  412. char escaped_comment[COMMENT_LENGTH * 2 + 1];
  413. Sql_EscapeString( nullptr, escaped_comment, entry.comment.c_str() );
  414. condition += " AND `comment` like '%" + std::string( escaped_comment ) + "%'";
  415. }
  416. std::vector<s_party_booking_entry> bookings;
  417. if( !party_booking_read( world_name, bookings, condition, " ORDER BY `created` DESC" ) ){
  418. res.status = HTTP_BAD_REQUEST;
  419. res.set_content( "Error", "text/plain" );
  420. return;
  421. }
  422. std::string response;
  423. response = "{ \"Type\": 1, \"totalPage\": ";
  424. response += std::to_string( bookings.size() );
  425. response += ", \"data\": [";
  426. for( size_t i = 0, max = bookings.size(); i < max; i++ ){
  427. s_party_booking_entry& booking = bookings.at( i );
  428. response += booking.to_json( world_name );
  429. if( i < ( max - 1 ) ){
  430. response += ", ";
  431. }
  432. }
  433. response += "] }";
  434. res.set_content( response, "application/json" );
  435. }