diff --git a/server/flasgger/comments.yml b/server/flasgger/comments.yml new file mode 100644 index 00000000..7f1c1b63 --- /dev/null +++ b/server/flasgger/comments.yml @@ -0,0 +1,47 @@ +Comment: + type: object + description: Comment on a post + properties: + post_id: + required: true + type: string + description: Id of the parent post + example: abcde12345 + content: + required: true + type: string + description: Comment content + example: I also have that question! + postedby: + required: true + $ref: "#/definitions/PostedBy" + endorsed: + required: true + type: boolean + description: If the instructor endorsed this comment + default: false + example: true + replies: + required: true + type: array + items: + $ref: "#/definitions/Reply" + reactions: + required: true + $ref: "#/definitions/Reactions" + +CommentBody: + type: object + description: A new comment sent from a client + properties: + content: + required: true + type: string + description: Comment content + example: I also have that question! + isAnonymous: + required: true + type: boolean + description: If the user wants the comment to be anonymous + default: false + example: true \ No newline at end of file diff --git a/server/flasgger/courses.yml b/server/flasgger/courses.yml new file mode 100644 index 00000000..e69de29b diff --git a/server/flasgger/posts.yml b/server/flasgger/posts.yml new file mode 100644 index 00000000..81d5515f --- /dev/null +++ b/server/flasgger/posts.yml @@ -0,0 +1,57 @@ +Post: + type: object + description: Posts with content created by users + properties: + _id: + type: string + description: Id of the user who created the post + example: abcde12345 + courseid: + type: string + description: Id of the course the post was created in + example: oFAeuzdT4VwKf3dHUFGhwu + postedby: + $ref: "#/definitions/PostedBy" + title: + type: string + description: Title of the post + example: Question about homework 3? + content: + type: string + description: Content of the post + example: This homework doesn't make any sense to me. What chapters does this cover in the book? + isInstructor: + type: boolean + default: false + description: Whether or not the post was created by an instructor + example: false + isPinned: + type: boolean + default: false + description: Whether or not the post is pinned + example: false + isPrivate: + type: boolean + default: false + description: Whether or not the post is private + example: false + instructorCommented: + type: boolean + default: false + description: Whether or not the instructor has responded to the post + example: false + reactions: + $ref: "#/definitions/Reactions" + comments: + type: integer + description: Number of comments on the post + default: 0 + example: 3 + createdDate: + type: string + description: Date the post was created + example: 2021-03-12 20:40:00.752000 + updatedDate: + type: string + description: Date the post was was last responded to or modified + example: 2021-03-13 21:20:00.322000 diff --git a/server/flasgger/replies.yml b/server/flasgger/replies.yml new file mode 100644 index 00000000..0dd17e4b --- /dev/null +++ b/server/flasgger/replies.yml @@ -0,0 +1,36 @@ +Reply: + type: object + description: Reply to a comment + properties: + _id: + type: string + required: true + description: Reply ID + example: ofjaepwofj324 + content: + type: string + required: true + description: Comment content + example: That is a great comment. Wow! + postedby: + required: true + $ref: "#/definitions/PostedBy" + reactions: + required: true + $ref: "#/definitions/Reactions" + +ReplyBody: + type: object + description: A new reply sent from a client + properties: + content: + required: true + type: string + description: Reply content + example: That's a great comment! + isAnonymous: + required: true + type: boolean + description: If the user wants the reply to be anonymous + default: false + example: true \ No newline at end of file diff --git a/server/flasgger/requests.yml b/server/flasgger/requests.yml new file mode 100644 index 00000000..48abe7fc --- /dev/null +++ b/server/flasgger/requests.yml @@ -0,0 +1,21 @@ +POSTPostBody: + type: object + properties: + title: + type: string + description: Title of post + example: Question about homework 3? + content: + type: string + description: Content of the post + example: This homework doesn't make any sense to me. What chapters does this cover in the book? + isPrivate: + type: boolean + default: false + description: Whether or not the post is private + example: false + isAnonymous: + type: boolean + default: false + description: Whether or not the post is anonymous + example: false diff --git a/server/flasgger/responses.yml b/server/flasgger/responses.yml new file mode 100644 index 00000000..66e5ecd5 --- /dev/null +++ b/server/flasgger/responses.yml @@ -0,0 +1,19 @@ +400Response: + type: object + description: Response to bad requests + properties: + errors: + type: list + description: List of errors related to failed request + example: ["Invalid courseid"] +403Response: + type: object + description: Response to unauthorized requests + properties: + errors: + type: list + description: List of authorization errors + example: + [ + "Resource access restricted: missing course permission(s) admin, seePrivate", + ] diff --git a/server/flasgger/shared.yml b/server/flasgger/shared.yml new file mode 100644 index 00000000..96e7b2d4 --- /dev/null +++ b/server/flasgger/shared.yml @@ -0,0 +1,33 @@ +Reactions: + type: object + description: Reaction types with arrays of _ids of the users who reacted + properties: + likes: + type: list + description: List of users who have liked the content + example: ["abcde12345"] +PostedBy: + type: object + description: Information about the user who posted the content + properties: + first: + type: string + description: First name of the user who posted the content + example: Alec + last: + type: string + description: Last name of the user who posted the content + example: Springel + _id: + type: string + description: _id or anonymousId of user who posted the content + example: abcde12345 + anonymous: + type: boolean + default: false + description: Whether or not the content is posted anonymously + example: false + picture: + type: string + description: Profile photo URL + example: "https://google.com/user123/photo123" diff --git a/server/flasgger/user.yml b/server/flasgger/user.yml new file mode 100644 index 00000000..5b38505a --- /dev/null +++ b/server/flasgger/user.yml @@ -0,0 +1,85 @@ +User: + type: object + properties: + _id: + type: string + description: Unique user identifier + example: abcde12345 + anonymousId: + type: string + description: Secondary unique user identifier + example: d1e2f3g4h5 + email: + type: string + description: Email address + example: cyrilfiggis@gmail.com + first: + type: string + description: First name + example: Cyril + last: + type: string + description: Last name + example: Figgis + picture: + type: string + description: URL of the user's avatar picture + example: https://avatars.githubusercontent.com/u/111111111?v=4 + courses: + type: array + items: + $ref: "#/definitions/UserCourse" +UserCourse: + type: object + properties: + course_id: + type: string + description: ID of the course the user joined + example: vytxeTZskVKR7C7WgdSP3d + course_name: + type: string + description: Name associated with the course + example: "CIS499 Advanced Debugging" + nickname: + type: string + description: Course nickname + example: "CIS499" + color: + type: string + description: Accent color used in course specific ui + example: "#ee55ee" + canPost: + type: boolean + description: If the user can post in the course + default: true + example: true + seePrivate: + type: boolean + description: If the user can see private posts in the course + default: false + example: false + canPin: + type: boolean + description: If the user user can pin posts + default: false + example: false + canRemove: + type: boolean + description: If the user can remove posts from the course + default: false + example: false + canEndorse: + type: boolean + description: If the user can endorse posts in the course + default: false + example: true + viewAnonymous: + type: boolean + description: If the user can view the identity of anonymous posters + default: false + example: true + admin: + type: boolean + description: If the user is an administrator of the course + default: false + example: false diff --git a/server/resources/comments.py b/server/resources/comments.py index 81b07bc5..41a0d584 100644 --- a/server/resources/comments.py +++ b/server/resources/comments.py @@ -15,7 +15,27 @@ class Comments(Resource): def get(self, post_id=None): - # Get all comments on post + """ + Retrieves all the comments responding to a specific post + --- + parameters: + - in: path + description: Id of a post + name: post_id + required: true + tags: + - Comments + responses: + 200: + description: Returns list of comments + schema: + type: array + items: + $ref: '#/definitions/Comment' + 400: + schema: + $ref: '#/definitions/400Response' + """ post = self.retrieve_post(post_id) if post is None: return abort(400, errors=["Bad post id"]) @@ -23,8 +43,31 @@ def get(self, post_id=None): return [self.serialize(comment) for comment in Comment.objects.raw({'post_id': post_id})] def post(self, post_id=None): - # Add comment to post - # Retrieving post + """ + Creates a new comment + --- + tags: + - Comments + parameters: + - in: path + description: Id of a post + name: post_id + required: true + - name: body + in: body + description: Submitted comment data + required: true + schema: + $ref: '#/definitions/CommentBody' + responses: + 200: + description: Returns created comment + schema: + $ref: '#/definitions/Comment' + 400: + schema: + $ref: '#/definitions/400Response' + """ print("here") post = self.retrieve_post(post_id) if post is None: @@ -61,8 +104,31 @@ def post(self, post_id=None): return result, 200 def put(self, post_id=None): - # Update comment - # Parse the request + """ + Updates a comment + --- + tags: + - Comments + parameters: + - in: path + description: Id of a post + name: post_id + required: true + - name: body + in: body + description: Submitted comment data + required: true + schema: + $ref: '#/definitions/CommentBody' + responses: + 200: + description: Returns updated comment + schema: + $ref: '#/definitions/Comment' + 400: + schema: + $ref: '#/definitions/400Response' + """ post = self.retrieve_post(post_id) parser = reqparse.RequestParser() @@ -97,8 +163,45 @@ def put(self, post_id=None): raise Exception(f'No comment with id') def delete(self, post_id=None): - # Delete comment - # Grabbing comment id + """ + Deletes a comment + --- + tags: + - Comments + parameters: + - in: path + description: Id of a post + name: post_id + required: true + - name: body + in: body + description: Data needed to delete a comment + required: true + schema: + type: object + properties: + _id: + type: string + description: Id of the comment + example: abcde12345 + responses: + 200: + description: Returns successful delete + schema: + type: object + properties: + deleted: + type: bool + example: True + 403: + description: Returns unsuccessful delete + schema: + type: object + properties: + deleted: + type: bool + example: False + """ post = self.retrieve_post(post_id) parser = reqparse.RequestParser() parser.add_argument('_id') diff --git a/server/resources/courses.py b/server/resources/courses.py index 52d80f4c..b30d1a84 100644 --- a/server/resources/courses.py +++ b/server/resources/courses.py @@ -24,6 +24,37 @@ class Courses(Resource): @permission_layer([]) def post(self): + """ + Creates a new course and responds with the instructor's permissions for the course + --- + tags: + - Courses + parameters: + - name: body + in: body + required: true + description: Course creation options + schema: + type: object + properties: + course: + type: string + description: Name of the course + example: CIS 422 + responses: + 200: + description: Stored user data for the currently logged in user + schema: + $ref: '#/definitions/UserCourse' + 400: + description: Array of errors gathered from request + schema: + $ref: '#/definitions/400Response' + 403: + description: Unable to retrieve current user data + schema: + $ref: '#/definitions/403Response' + """ # Get json for POST requests request.get_json(force=True) # Parse arguments diff --git a/server/resources/home.py b/server/resources/home.py index 72446eea..82ac91e8 100644 --- a/server/resources/home.py +++ b/server/resources/home.py @@ -17,6 +17,20 @@ class Home(Resource): def get(self): + """ + Retrieves all the posts in a users feed + --- + tags: + - Other + responses: + 200: + description: Returns list of posts + schema: + type: array + items: + $ref: '#/definitions/Post' + """ + # TODO: should I add this? Would you be able to handle this format on the front end or should I change it? if len(current_user.courses) == 0: return [] diff --git a/server/resources/join.py b/server/resources/join.py index c5e99a44..7f162302 100644 --- a/server/resources/join.py +++ b/server/resources/join.py @@ -15,7 +15,53 @@ class Join(Resource): def post(self): - # Parse arguments + """ + Begins adding user to course + --- + tags: + - Other + parameters: + - name: body + in: body + description: Course the user wants to join + required: true + schema: + type: object + properties: + course_name: + type: string + description: Name of the course + example: CIS 499 Advanced Debugging + access_code: + type: string + description: Access code for the course + example: ihuaweoliawfeh + responses: + 200: + description: Returns additional course information + schema: + type: object + properties: + course_name: + type: string + description: Name of the course + example: CIS 499 Advanced Debugging + course_id: + type: string + description: Id of the course + example: qewruiopq12313 + first: + type: string + description: Instructor first name + example: Sam + last: + type: string + description: Instructor last name + example: Peters + 400: + schema: + $ref: '#/definitions/400Response' + """ parser = reqparse.RequestParser() parser.add_argument('course_name') parser.add_argument('access_code') @@ -52,7 +98,39 @@ def post(self): return {"course_id": course._id, "course": course.course, "first": instructor.first, "last": instructor.last}, 200 def put(self): - # Parse arguments + """ + Confirms adding user to course + --- + tags: + - Other + parameters: + - name: body + in: body + description: Course the user wants to join + required: true + schema: + type: object + properties: + course_id: + type: string + description: Id of the course + example: abcde12389 + responses: + 200: + description: Returns successful delete + schema: + type: object + properties: + success: + required: true + type: string + example: Course joined successfully + course: + $ref: '#/definitions/UserCourse' + 400: + schema: + $ref: '#/definitions/400Response' + """ parser = reqparse.RequestParser() parser.add_argument('course_id') args = parser.parse_args() diff --git a/server/resources/me.py b/server/resources/me.py index 7c26dae2..43c4ca24 100644 --- a/server/resources/me.py +++ b/server/resources/me.py @@ -21,64 +21,14 @@ def get(self): tags: - User definitions: - User: - type: object - properties: - sub: - type: string - description: Unique user identifier - example: abcde12345 - email: - type: string - description: Email address - example: cyrilfiggis@gmail.com - first: - type: string - description: First name - example: Cyril - last: - type: string - description: Last name - example: Figgis - instructor: - type: boolean - default: false - description: Boolean representing if the user is an instructor on the platform - example: cyrilfiggis@gmail.com - permissions: - type: array - items: - $ref: '#/definitions/CoursePermissions' - - CoursePermissions: - type: object - properties: - course_id: - type: string - description: ID of the course these permissions are for - example: vytxeTZskVKR7C7WgdSP3d - post_question: - type: boolean - description: If the user is allowed to post a question - default: true - example: true - create_announcement: - type: boolean - description: If the user is allowed to post an announcement - default: false - example: false - post_response: - type: boolean - description: If the user is allowed to post a response to another post - default: true - example: true - 403Message: - type: object - properties: - msg: - type: string - description: Reason the request did not succeed or was rejected - example: "Resource access restricted" + import: "./flasgger/responses.yml" + import: "./flasgger/courses.yml" + import: "./flasgger/user.yml" + import: "./flasgger/shared.yml" + import: "./flasgger/posts.yml" + import: "./flasgger/comments.yml" + import: "./flasgger/replies.yml" + import: "./flasgger/requests.yml" responses: 200: @@ -88,9 +38,6 @@ def get(self): 403: description: Unable to retrieve current user data schema: - $ref: '#/definitions/403Message' - - - + $ref: '#/definitions/403Response' """ return current_user.to_son().to_dict() diff --git a/server/resources/posts.py b/server/resources/posts.py index 0f03acfe..942623a8 100644 --- a/server/resources/posts.py +++ b/server/resources/posts.py @@ -18,6 +18,36 @@ class Posts(Resource): def post(self, course_id=None): + """ + Creates a new post + --- + tags: + - Posts + parameters: + - in: path + name: course_id + description: Id of the course to post to + required: true + - name: body + in: body + required: true + description: Content and post options + schema: + $ref: '#/definitions/POSTPostBody' + responses: + 200: + description: Responds with the newly created post + schema: + $ref: '#/definitions/Post' + 400: + description: Array of errors gathered from request + schema: + $ref: '#/definitions/400Response' + 403: + description: Unable to retrieve current user data + schema: + $ref: '#/definitions/403Response' + """ course = current_user.get_course(course_id) if not course: return {"errors": ["You have not joined this course"]}, 403 @@ -60,6 +90,47 @@ def post(self, course_id=None): return result, 200 def get(self, course_id=None): + """ + Retrieves all the posts in a course + --- + tags: + - Posts + parameters: + - in: path + name: course_id + required: true + description: course id from which to retrieve posts + - in: query + name: filterby + schema: + type: string + enum: ['instructor', 'me', 'myupvoted'] + required: false + description: Filter posts by category + - in: query + name: sortby + schema: + type: string + enum: ['newest', 'oldest'] + required: false + default: 'newest' + description: Sort posts by age + responses: + 200: + description: Responds with array of posts + schema: + type: array + items: + $ref: '#/definitions/Post' + 400: + description: Array of errors gathered from request + schema: + $ref: '#/definitions/400Response' + 403: + description: Unable to retrieve current user data + schema: + $ref: '#/definitions/403Response' + """ # Get the search input and the current course req = request.args.get('search') page = request.args.get('page', default=0, type=int) @@ -124,6 +195,51 @@ def get(self, course_id=None): return result, 200 def delete(self, course_id=None): + """ + Deletes a post + --- + tags: + - Posts + parameters: + - in: path + name: course_id + required: true + description: Id of a course + - name: body + in: body + required: true + description: Content and post options + schema: + type: object + properties: + _id: + type: string + description: Id of the post + example: abcde12345 + responses: + 200: + description: Responds with success or failure + schema: + type: object + properties: + deleted: + type: boolean + description: Whether or not the post was deleted + example: true + 400: + description: Array of errors gathered from request + schema: + $ref: '#/definitions/400Response' + 403: + description: Unable to delete the post + schema: + type: object + properties: + deleted: + type: boolean + description: Whether or not the post was deleted + example: false + """ # Parse arguments parser = reqparse.RequestParser() parser.add_argument('_id') @@ -153,6 +269,36 @@ def delete(self, course_id=None): raise Exception(f'No post with id') def put(self, course_id): + """ + Edits a post + --- + tags: + - Posts + parameters: + - in: path + name: course_id + description: Id of the course to post to + required: true + - name: body + in: body + required: true + description: Content and post options + schema: + $ref: '#/definitions/POSTPostBody' + responses: + 200: + description: Responds with the newly edited post + schema: + $ref: '#/definitions/Post' + 400: + description: Array of errors gathered from request + schema: + $ref: '#/definitions/400Response' + 403: + description: Unable to retrieve current user data + schema: + $ref: '#/definitions/403Response' + """ # Parse the request parser = reqparse.RequestParser() parser.add_argument('title') diff --git a/server/resources/reactions.py b/server/resources/reactions.py index 981de076..4b40d372 100644 --- a/server/resources/reactions.py +++ b/server/resources/reactions.py @@ -14,6 +14,49 @@ class Reactions(Resource): def put(self): + """ + Responds with a reaction to a post, comment, or reply + --- + tags: + - Reactions + parameters: + - in: query + name: postid + schema: + type: string + example: "postid123" + required: true + description: Id of the post to react to + - in: query + name: commentid + schema: + type: string + example: "commentid321" + required: true + description: Id of the comment to react to + - in: query + name: replyid + schema: + type: string + example: "replyid321" + required: true + description: Id of the reply to react to + responses: + 200: + description: Responds with reactions object + schema: + type: array + items: + $ref: '#/definitions/Reactions' + 400: + description: Array of errors gathered from request + schema: + $ref: '#/definitions/400Response' + 403: + description: Unable to retrieve current user data + schema: + $ref: '#/definitions/403Response' + """ # Parse arguments postid = request.args.get('postid') commentid = request.args.get('commentid') diff --git a/server/resources/replies.py b/server/resources/replies.py index ea95767e..81871677 100644 --- a/server/resources/replies.py +++ b/server/resources/replies.py @@ -15,8 +15,35 @@ class Replies(Resource): def post(self, post_id=None, comment_id=None): - # Add comment to post - # Retrieving post + """ + Creates a new reply + --- + tags: + - Replies + parameters: + - in: path + description: Id of a post + name: post_id + required: true + - in: path + description: Id of a comment + name: comment_id + required: true + - name: body + in: body + description: Submitted reply data + required: true + schema: + $ref: '#/definitions/ReplyBody' + responses: + 200: + description: Returns created reply + schema: + $ref: '#/definitions/Reply' + 400: + schema: + $ref: '#/definitions/400Response' + """ post = self.retrieve_post(post_id) if post is None: return abort(400, errors=["Bad post id"]) @@ -55,7 +82,35 @@ def post(self, post_id=None, comment_id=None): return result, 200 def put(self, post_id=None, comment_id=None): - # Update comment + """ + Updates a reply + --- + tags: + - Replies + parameters: + - in: path + description: Id of a post + name: post_id + required: true + - in: path + description: Id of a comment + name: comment_id + required: true + - name: body + in: body + description: Submitted reply data + required: true + schema: + $ref: '#/definitions/ReplyBody' + responses: + 200: + description: Returns updated comment + schema: + $ref: '#/definitions/Comment' + 400: + schema: + $ref: '#/definitions/400Response' + """ post = self.retrieve_post(post_id) # Parse the request @@ -93,7 +148,49 @@ def put(self, post_id=None, comment_id=None): return result, 200 def delete(self, post_id=None, comment_id=None): - # Delete comment + """ + Deletes a reply + --- + tags: + - Replies + parameters: + - in: path + description: Id of a post + name: post_id + required: true + - in: path + description: Id of a comment + name: comment_id + required: true + - name: body + in: body + description: Data needed to delete a reply + required: true + schema: + type: object + properties: + _id: + type: string + description: Id of the reply + example: abcde12345 + responses: + 200: + description: Returns successful delete + schema: + type: object + properties: + deleted: + type: bool + example: True + 403: + description: Returns unsuccessful delete + schema: + type: object + properties: + deleted: + type: bool + example: False + """ post = self.retrieve_post(post_id) # Grabbing comment id