API.txt 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. Canvas Detail
  2. SOCKET ROUTING:
  3. url(r'ws/canvas/(?P<pk>\d+)/trial-idea/$', TrialIdeaConsumer),
  4. url(r'ws/canvas/(?P<pk>\d+)/idea/$', IdeaConsumer),
  5. url(r'ws/canvas/(?P<pk>\d+)/comment/$', CommentConsumer),
  6. url(r'ws/project/(?P<pk>\d+)/collab/$', CollabConsumer),
  7. url(r'ws/project/(?P<pk>\d+)/tag/$', TagConsumer),
  8. SOCKET DECLARATIONS:
  9. ideaSocket = new WebSocket(
  10. 'ws://' + window.location.host +
  11. '/ws/canvas/' + canvasPK + '/idea/'
  12. );
  13. commentSocket = new WebSocket(
  14. 'ws://' + window.location.host +
  15. '/ws/canvas/' + canvasPK + '/comment/'
  16. );
  17. tagSocket = new WebSocket(
  18. 'ws://' + window.location.host +
  19. '/ws/project/' + projectPK + '/tag/'
  20. );
  21. collabSocket = new WebSocket(
  22. 'ws://' + window.location.host +
  23. '/ws/project/' + projectPK + '/collab/'
  24. );
  25. SOCKET SUCCESS CALLBACKS
  26. data.data as each package returned from consumers has the data payload as data, and the function header as function, all automatically wrapped up *by* consumer as data
  27. payload returned in the format
  28. data {
  29. function: String,
  30. data: data {
  31. ...
  32. ...
  33. ...
  34. }
  35. }
  36. EXAMPLE CALLBACKS
  37. NOTE THAT CHECKING THE DATA FOR HTTP RESPONSES (FOR FAILURES SUCH AS 401 UNAUTHORIZED) IS NOT CURRENTLY IMPLEMENTED, BUT THEY ARE RETURNED BY THE CONSUMERS UNDER DATA
  38. /********************************************************************
  39. *********************************************************************
  40. CALLBACKS
  41. *********************************************************************
  42. *********************************************************************/
  43. /***********************************
  44. IDEA SOCKET
  45. ************************************/
  46. ideaSocket.onmessage = function(e){
  47. var data = JSON.parse(e.data);
  48. var f = data["function"];
  49. if (f.includes("typing")) {
  50. typingCallback(data.data, f);
  51. return;
  52. }
  53. switch(f) {
  54. case "modifyIdea": {
  55. editIdeaSuccessCallback(data.data);
  56. break;
  57. }
  58. case "addIdea": {
  59. var idea = data.data['idea'];
  60. newIdeaSuccessCallback(idea);
  61. break;
  62. }
  63. case "deleteIdea": {
  64. deleteIdeaSuccessCallback(data.data);
  65. break;
  66. }
  67. }
  68. };
  69. /***********************************
  70. COMMENT SOCKET
  71. ************************************/
  72. commentSocket.onmessage = function(e){
  73. var data = JSON.parse(e.data);
  74. var f = data["function"];
  75. switch(f) {
  76. case "addComment": {
  77. addCommentSuccessCallback(data.data);
  78. break;
  79. }
  80. case "deleteComment": {
  81. deleteCommentSuccessCallback(data.data);
  82. break;
  83. }
  84. case "resolveIndividualComment": {
  85. resolveIndividualCommentSuccessCallback(data.data);
  86. break;
  87. }
  88. case "resolveAllComments": {
  89. resolveAllCommentsSuccessCallback(data.data);
  90. break;
  91. }
  92. }
  93. };
  94. /***********************************
  95. TAG SOCKET
  96. ************************************/
  97. tagSocket.onmessage = function(e){
  98. var data = JSON.parse(e.data);
  99. var f = data["function"];
  100. // console.log(e.data);
  101. // console.log(data);
  102. switch(f) {
  103. case "addTag": {
  104. newTagSuccessCallback(data.data);
  105. break;
  106. }
  107. case "removeTag": {
  108. removeTagSuccessCallback(data.data);
  109. break;
  110. }
  111. case "deleteTag": {
  112. deleteTagSuccessCallback(data.data);
  113. break;
  114. }
  115. }
  116. };
  117. /***********************************
  118. COLLAB SOCKET
  119. ************************************/
  120. collabSocket.onmessage = function(e){
  121. var data = JSON.parse(e.data);
  122. var f = data["function"];
  123. switch(f) {
  124. case "promoteUser": {
  125. promoteUserSuccessCallback(data.data);
  126. break;
  127. }
  128. case "demoteUser": {
  129. demoteAdminSuccessCallback(data.data);
  130. break;
  131. }
  132. case "addUser": {
  133. addUserSuccessCallback(data.data);
  134. break;
  135. }
  136. case "deleteUser": {
  137. deleteUserSuccessCallback(data.data);
  138. break;
  139. }
  140. case "newActiveUser": {
  141. newActiveUserCallback(data.data);
  142. break;
  143. }
  144. case "removeActiveUser": {
  145. removeActiveUserCallback(data.data);
  146. break;
  147. }
  148. case "sendWholeList": {
  149. wholeListCallback(data.data);
  150. break;
  151. }
  152. }
  153. };
  154. DATABASE MODELS AND THEIR FIELDS:
  155. Project:
  156. title = models.CharField(max_length=25, db_index=True)
  157. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  158. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  159. is_public = models.BooleanField(default=False, db_index=True)
  160. admins = models.ManyToManyField(User, related_name='admins')
  161. users = models.ManyToManyField(User, related_name='users')
  162. # Owner (creator) for canvas - owner promotes / demotes admins and can delete the canvas
  163. owner = models.ForeignKey(User, related_name = 'owner', on_delete = models.CASCADE)
  164. Canvas:
  165. title = models.CharField(max_length=25, db_index=True)
  166. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  167. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  168. # 0 for Ethics, 1 for Business, 2 for Privacy (TBD)
  169. canvas_type = models.PositiveSmallIntegerField(default=0)
  170. tags = models.ManyToManyField('CanvasTag', related_name='canvas_set', blank=True)
  171. project = models.ForeignKey(Project, related_name='canvas_set', on_delete=models.CASCADE, default=0)
  172. Idea:
  173. title = models.CharField(max_length=50)
  174. text = models.CharField(max_length=255)
  175. # Default = 9 for uncategorised
  176. category = models.PositiveSmallIntegerField(default=9, db_index=True);
  177. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  178. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  179. canvas = models.ForeignKey('Canvas', on_delete=models.CASCADE)
  180. # M2M RELATION WITH IDEAS - A TAG CAN EXIST IN MANY IDEAS AND AN IDEA MAY CONTAIN MANY TAGS
  181. tags = models.ManyToManyField('CanvasTag', related_name='idea_set', blank=True)
  182. CanvasTag:
  183. label = models.CharField(max_length=25)
  184. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  185. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  186. IdeaComment:
  187. text = models.CharField(max_length=255, help_text="Type a comment")
  188. resolved = models.BooleanField(default=False)
  189. user = models.ForeignKey(User, on_delete=models.CASCADE)
  190. idea = models.ForeignKey(
  191. 'Idea', null = False,
  192. on_delete=models.CASCADE,
  193. related_name='comments',
  194. db_index=True
  195. )
  196. timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
  197. FUNCTIONS
  198. Add Idea
  199. TRIAL USER:
  200. trialIdeaSocket.send(JSON.stringify({
  201. 'category': the idea's category - index of the idea component in the list of components,
  202. }));
  203. if successful
  204. returns {
  205. idea: Idea model
  206. }
  207. LOGGED-IN USER:
  208. ideaSocket.send(JSON.stringify({
  209. 'function': 'addIdea',
  210. 'category': the idea's category - index of the idea component in the list of components,
  211. }));
  212. if successful
  213. returns {
  214. 'function': 'addIdea'
  215. 'idea': Idea model
  216. }
  217. if unsuccessful
  218. returns HttpResponse('Unauthorized', status = 401)
  219. }
  220. Everything below is for logged-in users only. Edit and delete ideas for trial users should just modify the element in the list, or remove it respectively. Websocket only required for add-idea in order to get a JSON of the Idea model.
  221. Edit Idea
  222. ideaSocket.send(JSON.stringify({
  223. 'function': 'modifyIdea',
  224. 'input_text': text,
  225. 'idea_pk': idea primary key,
  226. 'category': the idea's category,
  227. 'i': index of the idea in the idea component's list
  228. }));
  229. if successful
  230. returns {
  231. 'function': 'modifyIdea',
  232. 'idea': Idea Model,
  233. 'old_text': former text of idea - required to check if a tag was added or removed on the idea (newly present or newly removed),
  234. 'i': index of the idea in the idea component's list
  235. }
  236. if unsuccessful
  237. returns {
  238. HttpResponse('Unauthorized', status = 401)
  239. }
  240. }
  241. Delete Idea{
  242. if (tags in idea){
  243. tagSocket.send(JSON.stringify({
  244. 'function': 'removeTag',
  245. "idea_pk": idea.pk,
  246. "label": tags[t].fields.label,
  247. "canvas_pk": canvasPK,
  248. }));
  249. refer to tags description at bottom
  250. }
  251. ideaSocket.send(JSON.stringify({
  252. 'function': 'deleteIdea',
  253. 'idea_pk': idea primary key,
  254. 'i': index of the idea in the idea component's list
  255. }));
  256. if successful
  257. return {
  258. 'function': 'deleteIdea',
  259. 'i': index of the idea in the idea component's list,
  260. 'category': the idea's category,
  261. }
  262. if unsuccessful
  263. returns {
  264. HttpResponse('Unauthorized', status = 401)
  265. }
  266. }
  267. this exists for other users to see which user is currently typing on what idea
  268. Typing
  269. ideaSocket.send(JSON.stringify({
  270. 'function': 'typing',
  271. 'category': category of idea where typing is happening
  272. 'username': the user who is typing
  273. 'i': index of the idea in the idea component's list
  274. }))
  275. return {
  276. 'function': 'typing',
  277. 'category': category of idea where typing is happening
  278. 'username': the user who is typing
  279. 'i': index of the idea in the idea component's list
  280. }
  281. }
  282. Done Typing
  283. ideaSocket.send(JSON.stringify({
  284. 'function': 'done_typing',
  285. 'category': category of idea where typing is happening
  286. 'username': the user who is typing
  287. 'i': index of the idea in the idea component's list
  288. }));
  289. returns the same as Typing
  290. Add Comment
  291. commentSocket.send(JSON.stringify({
  292. 'function': 'addComment',
  293. 'input_text': comment's inputted text,
  294. 'i': index of the idea the comment is attached to in the idea component's list
  295. 'idea_pk': primary key of the idea the comment is attached to
  296. }));
  297. if successful
  298. return {
  299. 'function': 'addComment',
  300. 'i': index of the idea the comment is attached to in the idea component's list
  301. 'comment': Comment Model,
  302. 'category': category of idea the comment is attached to,
  303. }
  304. if unsuccessful
  305. returns {
  306. HttpResponse('Unauthorized', status = 401)
  307. }
  308. }
  309. Delete Comment
  310. commentSocket.send(JSON.stringify({
  311. 'function': 'deleteComment',
  312. "comment_pk": primary key of the victim comment
  313. 'i': index of the idea the comment is attached to in the idea component's list
  314. 'c': index of the comment in the comment component's list of comments
  315. }));
  316. if successful
  317. returns {
  318. 'function': 'deleteComment',
  319. 'category': category of idea where typing is happening
  320. 'i': index of the idea the comment is attached to in the idea component's list
  321. 'c': index of the comment in the comment component's list of comments
  322. }
  323. if unsuccessful
  324. returns HttpResponse('Forbidden', status = 403)
  325. }
  326. Resolve One Comment
  327. commentSocket.send(JSON.stringify({
  328. 'function': 'resolveIndividualComment',
  329. "comment_pk": primary key of the victim comment
  330. 'i': index of the idea the comment is attached to in the idea component's list
  331. 'c': index of the comment in the comment component's list of comments
  332. }));
  333. if successful
  334. return {
  335. 'function': 'resolveIndividualComment',
  336. 'category': category of idea where the comment is attached
  337. 'i': index of the idea the comment is attached to in the idea component's list
  338. 'c': index of the comment in the comment component's list of comments
  339. }
  340. if unsuccessful
  341. returns HttpResponse('Forbidden', status = 403)
  342. }
  343. Resolve All Comments
  344. commentSocket.send(JSON.stringify({
  345. 'function': 'resolveAllComments',
  346. "idea_pk": primary key of the idea where all comments are to be resolved
  347. 'i': index of the idea the comment is attached to in the idea component's list
  348. }));
  349. if successful
  350. return {
  351. 'function': 'resolveAllComments',
  352. 'i': index of the idea the comments are attached to in the idea component's list
  353. 'category': category of idea where the comment is attached
  354. }
  355. USER FUNCTIONALITY - ONLY MODIFIABLE IN PROJECT DETAIL VIEW (projectDetail.js)
  356. EACH CALLBACK, AND THE ADD/REMOVE ACTIVE USER SHOULD BE PRESENT IN CANVAS DETAIL VIEW (canvasDetail.js).
  357. THIS IS BECAUSE OTHER MODELS REQUIRE THE CURRENT STATE OF USERS AND ADMINS LISTS
  358. Add User to Project
  359. collabSocket.send(JSON.stringify({
  360. 'function': 'addUser',
  361. 'name': User's name
  362. }));
  363. if successful {
  364. returns {
  365. 'function': 'addUser',
  366. 'user': User Model - newly added user
  367. }
  368. }
  369. if unsuccessful {
  370. return HttpResponse('Unauthorized', status = 401) (not permitted action)
  371. or {
  372. if (user not found)
  373. reply = 'Error: ' + name + ' does not exist. Please try a different username.'
  374. elif (username is your own)
  375. reply = 'Error: you\'re already a collaborator, you can\'t add yourself!'
  376. elif (user already a collaborator)
  377. reply = 'Error: ' + name + ' is already a collaborator!'
  378. return HttpResponse(reply, status = 500)
  379. }
  380. }
  381. Delete User from Project
  382. collabSocket.send(JSON.stringify({
  383. 'function': 'deleteUser',
  384. 'user_pk': u.pk,
  385. 'ui': index of the user in the userList
  386. }));
  387. if successful {
  388. returns {
  389. 'function': 'deleteUser',
  390. 'victim_is_admin': boolean signifying if the victim user is an admin - should be removed from the admin list as well ,
  391. 'ui': index of the user in the userList
  392. }
  393. }
  394. if unsuccessful {
  395. if (user isn't an admin):
  396. return HttpResponse('Forbidden', status = 403)
  397. elif (victim user doesn't exist)
  398. reply = 'Error: ' + name + ' is not a collaborator'
  399. elif (self is only admin and trying to remove self)
  400. reply = 'Error: You are the only admin, you may not delete yourself!'
  401. return HttpResponse(reply, status = 500)
  402. }
  403. Promote User to Admin Status
  404. collabSocket.send(JSON.stringify({
  405. 'function': 'promoteUser',
  406. 'user_pk': primary key of user
  407. }));
  408. if successful {
  409. returns {
  410. 'function': 'promoteUser',
  411. 'admin': User Model - new admin
  412. }
  413. }
  414. if unsuccessful{
  415. returns {
  416. if (user not an admin):
  417. return HttpResponse('Forbidden', status = 403)
  418. elif (current user is the same as admin candidate user)
  419. name_str = 'you are'
  420. elif (admin candidate is an admin already:
  421. name_str = admin_name + ' is '
  422. reply = 'Error: ' + name_str + ' already an admin!'
  423. return HttpResponse(reply, status = 500)
  424. }
  425. }
  426. Demote User from Admin Status
  427. collabSocket.send(JSON.stringify({
  428. 'function': 'demoteUser',
  429. 'user_pk': the admin's primary key
  430. 'ai': index of admin in adminList
  431. }));
  432. if successful {
  433. returns {
  434. 'function': 'demoteUser',
  435. 'ai': index of admin in adminList
  436. }
  437. }
  438. if unsuccessful {
  439. returns {
  440. if (current user isn't an admin)):
  441. return HttpResponse('Forbidden', status = 403)
  442. elif (victim isn't an admin):
  443. reply = 'Error: ' + name + ' is not an admin'
  444. elif (admin list contains a single admin - implicitly the current user):
  445. reply = 'Error: You are the only admin, you may not demote yourself!'
  446. return HttpResponse(reply, status = 500)
  447. }
  448. }
  449. Add Active User
  450. collabSocket.onopen = function(e){
  451. collabSocket.send(JSON.stringify({
  452. "function": "newActiveUser",
  453. "user": currently logged-in user,
  454. }));
  455. };
  456. if successful {
  457. returns {
  458. 'function': 'newActiveUser',
  459. 'user': User Model - the current user,
  460. }
  461. Callback performs the sendWholeList request. The new user who just added themselves to the active users needs to gain knowledge of all the other users who are active on the project.
  462. It is from these other users that the current user gets this knowledge, as their newActiveUserCallback triggers sendWholeList with their lists, and every user's sendWholeListCallback
  463. appends the unique users to their own list
  464. List of all users - called
  465. collabSocket.send(JSON.stringify({
  466. 'function': 'sendWholeList',
  467. 'users': every active user,
  468. }));
  469. if successful {
  470. returns {
  471. 'function': function,
  472. 'users': List of User Models - every user the current instance knows about,
  473. }
  474. if unsuccessful {
  475. doesn't return anything
  476. }
  477. }
  478. }
  479. if unsuccessful {
  480. doesn't return anything
  481. }
  482. Remove Active User
  483. window.onbeforeunload = function(e){
  484. collabSocket.send(JSON.stringify({
  485. "function": "removeActiveUser",
  486. "user": the currently logged-in user,
  487. }));
  488. collabSocket.close();
  489. };
  490. if successful {
  491. returns {
  492. 'function': 'removeActiveUser',
  493. 'user': User Model - the current user,
  494. }
  495. }
  496. if unsuccessful {
  497. doesn't return anything
  498. }
  499. Toggle Public Project
  500. collabSocket.send(JSON.stringify({
  501. 'function': 'togglePublic',
  502. 'project_pk': primary key of the project
  503. }))
  504. };
  505. if successful {
  506. doesn't return anything
  507. }
  508. if unsuccessful {
  509. returns {
  510. if (current user isn't an admin):
  511. return HttpResponse('Forbidden', status = 403)
  512. }
  513. }
  514. addTag(str function, str label, int canvas_pk){
  515. if successful {
  516. returns {
  517. 'function': 'addTag'
  518. 'data'{
  519. 'taggedCanvases': Canvas Model List - all canvasses tagged by the current tag,
  520. 'taggedIdeas': Idea Model List - all ideas tagged by the current tag,
  521. 'tag': CanvasTag Model - the tag added,
  522. }
  523. }
  524. }
  525. if unsuccessful {
  526. returns {
  527. if (user isn't a user of the canvas):
  528. return HttpResponse('Unauthorized', status = 401)
  529. }
  530. }
  531. }
  532. removeTag(str function, int i,int tag_pk,int canvas_pk){
  533. if successful {
  534. returns {
  535. 'function': 'removeTag'
  536. 'data' {
  537. 'taggedCanvases': Canvas Model List - all canvasses tagged by the current tag,
  538. 'taggedIdeas': Idea Model List - all ideas tagged by the current tag,
  539. 'tag': CanvasTag Model - the tag removed,
  540. }
  541. }
  542. }
  543. if unsuccessful {
  544. returns {
  545. if (user isn't a user of the canvas):
  546. return HttpResponse('Unauthorized', status = 401)
  547. }
  548. }
  549. }
  550. deleteTag(str function, int i, int tag_pk){
  551. if successful {
  552. returns {
  553. 'function': 'deleteTag'
  554. data: {
  555. 'tag': CanvasTag Model - the tag to be deleted,
  556. }
  557. }
  558. }
  559. if unsuccessful {
  560. returns {
  561. if (user isn't a user of the canvas):
  562. return HttpResponse('Unauthorized', status = 401)
  563. }
  564. }
  565. }