Skip to content
This repository has been archived by the owner on Dec 10, 2024. It is now read-only.

Added Structure for Backend Routing, Middleware, and More Documentation #69

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 44 additions & 44 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,66 +22,66 @@ Some terminology:

### Structure

userRouter := router.Group("/user"){
```text
.
├── backend
| |── httpd
| | |── actions # Contains the routes for a registered user, actions include: enrollment, post creation, replies, etc.
| | | └── actionsRoutes.go
| | |── auth
| | | └── authRoutes.go # Contains the authRouter functions Login, and Register
| | |── middleware
| | | |── instructorMiddleware.go # Contains middleware for authenticating instructors and their privileges
| | | └── studentMiddleware.go # Contains middleware for authentication the student is registered, and the actions are valid
| | └── main.go
| |
..................
```

userRouter.POST("/register", handleUserRegister)
// The request respondst to the url matching "/user/register" and should include
// information such as first_name, last_name, email. How is uid being generated? will it be given to us in the request or is is
// it made automatically in the database and sent to the front end in a different call
### Auth

userRouter.POST("/login", handleUserLogin)
// The request responds to the url matching "/user/login" and should include
// parameters we require for authentication (this will be done later), we can send the uid to the front end as a response here
File: authRoutes.go

Should we add a reset password for a user route when we add authentication ? (Y)
Functions:

userRouter.POST("/resetPassword", handleResetPassword)
// The request responds to the url matching "/user/resetPassword" and should include the users email
// in the request
- Login
- Register

}
Methods:

The following are all routes for a specific user based on their _uid_. Certain routes should only be accessible to professors, e.g. course creation, and archive course.
- POST

specificUserRouter := router.Group("/user/:uid"){
The _authRoutes_ file contains the functions for login and registering users. The required parameters and JSON format are
provided in the comments of the functions.

**uid is a prerequiste for all requests below**
The functions all correspond to urls of the form /auth/*type, where *type is either login or register.

specificUserRouter.POST("/enroll", handleEnrollment)
// The request responds to url matching: "/user/:uid/enroll?course=:cid"
### Actions

specificUserRouter.POST("/:cid/createPost", handleCreatePost)
// The request responds to url matching "/user/:uid/:cid/createPost"
File: actionsRoutes.go

specificUserRouter.POST("/:cid/replyToPost", handleReplyToPost)
// This route can be used for a user replying to posts made,
// the request should include information about the post that can be used to find it (tid?)
Functions:

specificUserRouter.POST("/:cid/:tid/createComment", handleComment)
// The request responds to the url matching: "/user/:uid/:cid/:tid/createComment"
// Since each comment is specific to a thread in a specific course we will require cid, and tid for comments along with the prerequiste uid as well
- Enroll
- CreatePost
- Reply
- Comment
- DeletePost
- Upvote
- CreateCourse
- ArchiveCourse

specificUserRouter.DELETE("/:cid/:tid", handleDeletePost)
// The request responds to the url matching "/users/:uid/:cid/:tid/deletePost"
// Middleware will be required to make sure only the author of the post, or an instructor is able to delete the post
// for author authentication we can check the uid supplied with the author entry of the thread
Methods:

specificUserRouter.PATCH("/:cid/:tid/:comid/upvote", handleUpvote)
// The request responds to the url matching "/users/:uid/:cid/:tid/:comid/upvote"
// This is an update method that allows users to upvote comments on a given post.
- PATCH
- POST
- DELETE

Do we make some middleware for the routes below to make sure the user here is a professor and is allowed to create/archive courses?
The _actionRoutes_ file contains the functions for user actions. The required parameters and JSON format are
provided in the comments of the functions. The functions above have authentication middleware to determine whether certain users
have permisson to commit certain actions.

specificUserRouter.POST("/createCourse", handleCourseCreation)
// This request responds to url matching "/user/:uid/createCourse"
// This can be used by a professor to create a course, we require cid ? (how is this generated), cname, ccode, semid, and isarc
// for the db entry

specificUserRouter.POST("/:cid/archiveCourse", handleArchiveCourse)
// This request responds to the url matching "/user/:uid/:cid/archiveCourse"
// and is used for archiving a course in the db, we require the cid for looking up the course in the db, and we can just update the isarc value to be true

}
The functions all correspond to urls of the form /user/:uid, where uid is the user id.

### CORS

Expand Down
156 changes: 156 additions & 0 deletions backend/httpd/actions/actionsRoutes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package actions

import (
"net/http"

"github.com/gin-gonic/gin"
)

// Course struct for binding JSON to
// JSON: {cname: "course name", ccode: "CSC4000", cpass: "course password", semid: semesterID, isarc: false}
type Course struct {
CourseName string `json:"cname" binding:"required"`
CourseCode string `json:"ccode" binding:"required"`
CoursePassword string `json:"cpass" binding:"required"`
SemesterID int `json:"semid" binding:"required"`
IsArc bool `json:"isarc" binding:"required"`
}

// Enroll Handler Function
// Method: POST
// url: /user/:uid/enroll/:cid"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid,
// - Param: cid,
func Enroll(c *gin.Context) {
userID := c.Param("uid")
courseID := c.Param("cid")

// TODO:
// 1. Add user to enrolment table with cid and position type student

c.JSON(http.StatusOK, gin.H{"status": "You have been enrolled", "user": userID, "course": courseID})
}

// CreatePost Handler Function
// Method: POST
// url: /user/:uid/:cid/createPost"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid,
// - Param: cid,
func CreatePost(c *gin.Context) {
userID := c.Param("uid")
courseID := c.Param("cid")

// TODO:
// 1. Create Thread with user as author

c.JSON(http.StatusOK, gin.H{"status": "You have created a Post!", "user": userID, "course": courseID})
}

// Reply Handler Function
// Method: POST
// url: /user/:uid/:cid/replyToPost/:tid"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid,
// - Param: cid,
// - Query: tid,
func Reply(c *gin.Context) {
userID := c.Param("uid")
courseID := c.Param("cid")
threadID := c.Param("tid")
// TODO:
// 1. Add reply to thread with user as author to reply

c.JSON(http.StatusOK, gin.H{"status": "You have created a Post!", "user": userID, "course": courseID, "threadID": threadID})
}

// Comment Handler Function
// Method: POST
// url: /user/:uid/:cid/:tid/comment"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid,
// - Param: cid,
// - Param: tid,
func Comment(c *gin.Context) {
userID := c.Param("uid")
courseID := c.Param("cid")
threadID := c.Param("tid")
// TODO:
// 1. Find thread/post
// 2. Add comment to post with user as author

c.JSON(http.StatusOK, gin.H{"status": "You have created a Post!", "user": userID, "course": courseID, "threadID": threadID})
}

// DeletePost Handler Function
// Method: DELETE
// url: /user/:uid/:cid/:tid/deletePost"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid,
// - Param: cid,
// - Param: tid,
func DeletePost(c *gin.Context) {
userID := c.Param("uid")
courseID := c.Param("cid")
threadID := c.Param("tid")
// TODO:
// 1. Find post
// 2. Delete Post from database

c.JSON(http.StatusOK, gin.H{"status": "You have created a Post!", "user": userID, "course": courseID, "threadID": threadID})
}

// Upvote Handler Function
// Method: PATCH
// url: /user/:uid/:cid/:tid/:comid/upvote"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid,
// - Param: cid,
// - Param: tid,
// - Param: comid,
func Upvote(c *gin.Context) {
userID := c.Param("uid")
courseID := c.Param("cid")
threadID := c.Param("tid")
commentID := c.Param("comid")
// TODO:
// 1. Add upvote to the comment, and update anything related to the comment

c.JSON(http.StatusOK, gin.H{"status": "You have created a Post!", "user": userID, "course": courseID, "threadID": threadID, "commentID": commentID})
}

// CreateCourse Handler Function
// Method: POST
// url: /user/:uid/createCourse"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid
// - We require course information in json format
// JSON: {cname: "course name", ccode: "CSC4000", cpass: "course password", close: "closing at", semid: semesterID, isarc: false}
func CreateCourse(c *gin.Context) {
userID := c.Param("uid")
var course Course
if err := c.ShouldBindJSON(&course); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// TODO:
// 1. Create Course from model
// 2. Add to db
c.JSON(http.StatusOK, gin.H{"status": "You have created a Post!", "user": userID})
}

// ArchiveCourse Handler Function
// Method: PATCH
// url: /user/:uid/:cid/archiveCourse"
// Requirements: (can be changed later, this is just the skeleton)
// - Param: uid
// - We require course information in json format
func ArchiveCourse(c *gin.Context) {
userID := c.Param("uid")
courseID := c.Param("cid")
// TODO:
// 1. Find course with cid
// 2. Update its is_archived parameter to be false
c.JSON(http.StatusOK, gin.H{"status": "You have created a Post!", "user": userID, "course": courseID})
}
63 changes: 63 additions & 0 deletions backend/httpd/auth/authRoutes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package auth

import (
"net/http"

"github.com/gin-gonic/gin"
)

// ExistingUser Binding from JSON for Login
type ExistingUser struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}

// NewUser Binding from JSON for Register
type NewUser struct {
Firstname string `json:"firstname" binding:"required"`
Lastname string `json:"lastname" binding:"required"`
Email string `json:"email" binding:"required"`
}

// Login Handler Function
// Method: POST
// url: /auth/login
// Requirements: (can be changed later, this is just the skeleton)
// - email
// - password
// In JSON format: ({"email": "email@mail.com", "password": "password"})
func Login(c *gin.Context) {
var user ExistingUser
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// TODO:
// 1. Authenticate the user based on their info
// 2. "Log them in" if successful, i.e., return relevant information to frontend
// else, we return error message such as "User does not exist"
c.JSON(http.StatusOK, gin.H{"status": "Logged In", "email": user.Email})
}

// Register Handler Function
// Method: POST
// url: /auth/register
// RequirementsL
// - firstname
// - lastname
// - email
// - password (This will be added later on, once password field is added to DB, and updated in the user model)
// in JSON format: ({"firstname":"first", "lastname":"last", "email":"email"})
func Register(c *gin.Context) {
var user NewUser
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// TODO:
// 1. Create the user model (use the model in backend/storage)
// 2. Add user to db using GORM
// 3. Log in the user automatically, ie., send the frontend all the relevant information
// you would when a user logs in.
c.JSON(http.StatusOK, gin.H{"status": "You have Registered!", "email": user.Email})
}
35 changes: 35 additions & 0 deletions backend/httpd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"

Actions "pizza/httpd/actions"
Auth "pizza/httpd/auth"
Middleware "pizza/httpd/middleware"
)

func main() {
Expand Down Expand Up @@ -36,6 +40,37 @@ func main() {
})
})

// This is the router group for matching all urls with /auth
// The functions are all contained inside the auth directory's routes.go
authRouter := r.Group("/auth")
{
// Matches url /auth/login and uses the Login function to handle the request
authRouter.POST("/login", Auth.Login)
// Matches url /auth/register and uses the Register function to handle the request
authRouter.POST("/register", Auth.Register)
}

// This is the router group for matching all urls with /user/:uid
userRouter := r.Group("/user/:uid")
{
// TODO: Authentication Middleware for uid and cid
userRouter.POST("/enroll/:cid", Middleware.IsStudent(), Actions.Enroll)
userRouter.POST("/:cid/createPost", Middleware.IsStudent(), Actions.CreatePost)
// TODO: Authentication Middleware for uid, cid, and tid: Required
userRouter.POST("/:cid/replyToPost/:tid", Middleware.IsStudent(), Middleware.AbleToReply(), Actions.Reply)
userRouter.POST("/:cid/:tid/comment", Actions.Comment)
userRouter.DELETE("/:cid/:tid/deletePost", Actions.DeletePost)
// TODO: Authentication Middleware for uid, cid, tid, comid: Required
userRouter.PATCH("/:cid/:tid/:comid/upvote", Actions.Upvote)

// These two are special routes available only to instructors for creating courses and archiving them.
// TODO: Authentication Middleware to check uid belongs to instructor thus they have "create course" privileges
userRouter.POST("/createCourse", Middleware.IsInstructor(), Actions.CreateCourse)
// TODO: Authentication Middleware to check uid belongs to instructor thus they have "archive course" privileges, and
// check course with cid actually exists, check if the instructor is the instructor of course with id cid
userRouter.PATCH("/:cid/archiveCourse", Middleware.IsInstructor(), Middleware.InstructorIsAuthor(), Actions.ArchiveCourse)
}

err := r.Run() // listen and serve on 0.0.0.0:3001 (for windows "localhost:3001")
if err != nil {
panic("Failed to start server")
Expand Down
Loading