Skip to content

Commit

Permalink
Merge pull request #23 from GenerateNU/following_crud
Browse files Browse the repository at this point in the history
Following crud completed
  • Loading branch information
CamPlume1 authored Feb 27, 2024
2 parents 91d255a + d794828 commit c497a91
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ backend-ngrok:
# Test the backend
.PHONY: backend-test
backend-test:
cd backend
cd backend
4 changes: 4 additions & 0 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

_ "backend/docs"


"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
Expand All @@ -32,6 +33,7 @@ func main() {

r := gin.Default()


// Add CORS middleware
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
Expand All @@ -44,6 +46,8 @@ func main() {
routes.SetupUserRoutes(r, db, clerkClient)
routes.SetupETradeRoutes(r, db)
routes.SetupOnboardingRoutes(r, db)
routes.SetupFollowingRoutes(r, db)


r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

Expand Down
171 changes: 171 additions & 0 deletions backend/src/controllers/followings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package controllers

import (
"backend/src/models"
"backend/src/services"
"net/http"
"strconv"

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

////////////////////////////// Types + Constructors/////////////////////////

type FollowingController struct {
followingService *services.FollowingService
}

func NewFollowingController(followingService *services.FollowingService) *FollowingController {
return &FollowingController{
followingService: followingService,
}
}

///////////////////////Read//////////////////////////////////////////////////

// GetAllFollowings godoc
//
// @Summary Gets all followings relations
// @Description Returns all followings relations as objects
// @ID get-all-followings
// @Tags followings
// @Produce json
// @Success 200 {object} []models.Followings
// @Failure 404 {string} string "Failed to fetch followers: 404 Error"
// @Router /api/following/ [get]
func (fol *FollowingController) GetAllFollowings(c *gin.Context) {
followings, err := fol.followingService.GetAllFollowings()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users", "msg": err})
return
}

c.JSON(http.StatusOK, followings)
}

// GetTimeline godoc
//
// @Summary Gets all users followed by an input user
// @ID get-all-user-followings
// @Tags user-followings
// @Produce json
// @Success 200 {object} []models.Users
// @Failure 404 {string} string "Failed to fetch followers: 404 Error"
// @Router /api/following/ [get]
func (fol *FollowingController) GetTimeline(c *gin.Context) {
user, err := strconv.ParseUint(c.Param("follower_user_id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid follower_user_id"})
return
}
followings, err := fol.followingService.GetTimeline(uint(user))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
return
}

c.JSON(http.StatusOK, followings)
}

// GetFollowers godoc
//
// @Summary Gets all users following an input user
// @ID get-all-user-followers
// @Tags user-followings
// @Produce json
// @Param followed_user_id path uint true "ID of the user being followed"
// @Success 200 {object} []models.Users
// @Failure 404 {string} string "Failed to fetch followers: 404 Error"
// @Router /api/followers/{followed_user_id} [get]
func (fol *FollowingController) GetFollowers(c *gin.Context) {
user, err := strconv.ParseUint(c.Param("following_user_id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid following_user_id"})
return
}
followings, err := fol.followingService.GetFollowers(uint(user))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch users"})
return
}

c.JSON(http.StatusOK, followings)
}

//////////////////////////////////////////Create////////////////////////////////////////

// CreateFollowings godoc
//
// @Summary Creates a Followings relation
// @ID create-followings
// @Tags followings
// @Accept json
// @Produce json
// @Param request body CreateFollowingsRequest true "Request body for creating a Followings relation"
// @Success 200 {string} string "Following created successfully"
// @Failure 400 {string} string "Bad Request"
// @Failure 500 {string} string "Internal Server Error"
// @Router /api/followings/ [post]
func (fol *FollowingController) CreateFollowings(c *gin.Context) {

// Define Struct for JSON binding
var requestBody struct {
Follower uint `json:"follower_user_id"`
Followed uint `json:"following_user_id"`
}
// Bind the JSON body to the struct
if err := c.ShouldBindJSON(&requestBody); err != nil {
// If there was an error parsing the JSON, return a Bad Request
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(), "type": "json encoding"})
return
}

// Create a new Followings instance
following := models.NewFollowings(requestBody.Follower, requestBody.Followed)

// Call following service
var err = fol.followingService.CreateFollowings(following)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create following Here", "extra info": err})
return
}
//Success
c.JSON(http.StatusOK, gin.H{"msg": "Following created successfully"})
}

///////////////////////////////Delete////////////////////////////////

// UnfollowUser godoc
//
// @Summary Unfollows a user
// @ID unfollowUser
// @Tags followings
// @Produce json
// @Param follower_user_id path uint true "ID of the follower user"
// @Param followed_user_id path uint true "ID of the user to unfollow"
// @Success 200 {string} string "User unfollowed successfully"
// @Failure 404 {string} string "Failed to unfollow user: 404 Error"
// @Router /api/followings/{follower_user_id}/{followed_user_id} [delete]
func (fol *FollowingController) UnfollowUser(c *gin.Context) {

// Convert follower_user_id and followed_user_id to uint
follower, err := strconv.ParseUint(c.Param("follower_user_id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid follower_user_id"})
return
}

followed, err := strconv.ParseUint(c.Param("following_user_id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid following_user_id"})
return
}

err = fol.followingService.DeleteFollowing(uint(follower), uint(followed))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unfollow user", "error info": err})
return
}

c.JSON(http.StatusOK, gin.H{"msg": "User unfollowed successfully"})
}
14 changes: 7 additions & 7 deletions backend/src/db/migrations/2_FOLLOWING_V1.sql
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
-- Create Following table
DROP TABLE IF EXISTS following;
DROP TABLE IF EXISTS followings;

CREATE TABLE IF NOT EXISTS following (
following_id SERIAL PRIMARY KEY,
CREATE TABLE IF NOT EXISTS followings (
id SERIAL PRIMARY KEY,
follower_user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
following_user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
follow_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_following_pair UNIQUE (follower_user_id, following_user_id),
CONSTRAINT no_self_follow CHECK (follower_user_id != following_user_id)
);

CREATE INDEX IF NOT EXISTS idx_follower_user_id ON following(follower_user_id);
CREATE INDEX IF NOT EXISTS idx_following_user_id ON following(following_user_id);
CREATE INDEX IF NOT EXISTS idx_follower_user_id ON followings(follower_user_id);
CREATE INDEX IF NOT EXISTS idx_following_user_id ON followings(following_user_id);

-- Insert sample data into "following" table
INSERT INTO following (follower_user_id, following_user_id)
INSERT INTO followings (follower_user_id, following_user_id)
VALUES
(1, 2),
(2, 1);
25 changes: 25 additions & 0 deletions backend/src/models/followings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package models

import "time"

// Followings represents a following relationship between two users.
// It defines the structure of a following entry in the database.
type Followings struct {
FollowerUserID uint `gorm:"type:int;" json:"follower_user_id" validate:"required"`
FollowedUserID uint `gorm:"type:int;" json:"following_user_id" validate:"required"`
CreatedAt time.Time `gorm:"type:timestamp" json:"created_at" validate:"required"`

//User objects: User must be preloaded for gorm reads
FollowerUser User `gorm:"foreignKey:FollowerUserID;references:ID"`
FollowedUser User `gorm:"foreignKey:FollowedUserID;references:ID"`
}

// NewFollowings creates a new instance of Followings with the provided follower and followed user IDs.
// It initializes the struct fields and returns a pointer to the newly created Followings object.
func NewFollowings(followerID, followedID uint) *Followings {
return &Followings{

FollowerUserID: followerID,
FollowedUserID: followedID,
}
}
44 changes: 44 additions & 0 deletions backend/src/routes/followings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package routes

import (
"backend/src/controllers"
"backend/src/services"

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

// SetupFollowingRoutes sets up routes related to followings, timelines, and followers.
//
// Parameters:
// - router: A pointer to a Gin Engine instance where routes will be set up.
// - db: A pointer to algo db instance
//
// Returns: None.
func SetupFollowingRoutes(router *gin.Engine, db *gorm.DB) {
followingService := services.NewFollowingService(db)
followingController := controllers.NewFollowingController(followingService)

followingRoutes := router.Group("/followings")
{
//Get all following relations: Done
followingRoutes.GET("/", followingController.GetAllFollowings)
// Create a following relation: Done
followingRoutes.POST("", followingController.CreateFollowings)
//Delete a following relation
followingRoutes.DELETE("/:follower_user_id/:following_user_id", followingController.UnfollowUser)

}
timelineRoutes := router.Group("/timelines")
{
// Get a user timeline
timelineRoutes.GET("/:follower_user_id", followingController.GetTimeline)
}

followersRoutes := router.Group("/followers")
{
// Get all of a users followers
followersRoutes.GET("/:following_user_id", followingController.GetFollowers)
}

}
1 change: 0 additions & 1 deletion backend/src/routes/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func SetupUserRoutes(router *gin.Engine, db *gorm.DB, clerkClient clerk.Client)
userRoutes.PUT("/:id", userController.UpdateUserById)
userRoutes.DELETE("/:id", userController.DeleteUserById)


userRoutes.POST("/long-term-goal/:user_id", userController.CreateLongTermGoalForUser)
userRoutes.GET("/long-term-goal/:user_id/", userController.GetLongTermGoalsForUser)
userRoutes.PUT("/long-term-goal/:user_id/:goal_id", userController.UpdateLongTermGoalForUser)
Expand Down
Loading

0 comments on commit c497a91

Please sign in to comment.