partybooking_controller.cpp 15 KB

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