Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add category collection #19

Merged
merged 14 commits into from
Sep 28, 2024
Merged
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
193 changes: 193 additions & 0 deletions peer-prep-be/src/controllers/categories.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package controllers

import (
"context"
"net/http"
"time"

"github.com/CS3219-AY2425S1/cs3219-ay2425s1-project-g01/peer-prep-be/src/configs"
"github.com/CS3219-AY2425S1/cs3219-ay2425s1-project-g01/peer-prep-be/src/models"
"github.com/CS3219-AY2425S1/cs3219-ay2425s1-project-g01/peer-prep-be/src/responses"
"github.com/labstack/echo/v4"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

var categoriesCollection *mongo.Collection = configs.GetCollection(configs.DB, "categories")

func GetCategories(c echo.Context) error {

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

cursor, err := categoriesCollection.Find(ctx, bson.M{})

if err != nil {
return c.JSON(http.StatusInternalServerError, responses.StatusResponse{
Status: http.StatusInternalServerError,
Message: err.Error(),
})
}

var categories []models.Category
if err = cursor.All(ctx, &categories); err != nil {
return c.JSON(http.StatusInternalServerError, responses.StatusResponse{
Status: http.StatusInternalServerError,
Message: err.Error(),
})
}

return c.JSON(http.StatusOK, responses.StatusResponse{
Status: http.StatusOK,
Message: "Success",
Data: &echo.Map{"categories": categories},
})
}

func CreateCategory(c echo.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
// var existingCategory models.Category
var category models.Category
defer cancel()

if err := c.Bind(&category); err != nil {
return c.JSON(http.StatusBadRequest, responses.StatusResponse{
Status: http.StatusBadRequest,
Message: err.Error(),
})
}

// Validate Category Name
if validationErr := validate.Struct(&category); validationErr != nil {
return c.JSON(http.StatusBadRequest, responses.StatusResponse{
Status: http.StatusBadRequest,
Message: errMessage,
Data: &echo.Map{"data": validationErr.Error()},
})
}

// Duplicate handling
// err := categoriesCollection.FindOne(ctx, bson.M{"categoryName": category.CategoryName}).Decode(&existingCategory)
// if err == nil {
// return c.JSON(http.StatusBadRequest, responses.StatusResponse{Message: "Category already exists"})
// }

newCategoryId := primitive.NewObjectID()

newCategory := models.Category{
Category_id: newCategoryId,
Category_name: category.Category_name,
}

// Insert new if it does not exist, case insensitive
updateResult, err := categoriesCollection.UpdateOne(ctx,
bson.M{"category_name": newCategory.Category_name}, // Filter by category_name only, not the obj
bson.M{"$setOnInsert": bson.M{
"category_id": newCategory.Category_id,
"category_name": newCategory.Category_name,
}},
options.Update().SetUpsert(true).SetCollation(&options.Collation{
Locale: "en",
Strength: 2, // Case-insensitive comparison
}),
)

if err != nil {
return c.JSON(http.StatusInternalServerError, responses.StatusResponse{
Status: http.StatusInternalServerError,
Message: err.Error(),
})
}

updateResult.UpsertedID = newCategoryId;

// Check if a new category was created or if it was already existing
if updateResult.UpsertedCount > 0 {
// A new category was created
return c.JSON(http.StatusCreated, responses.StatusResponse{
Status: http.StatusCreated,
Message: "Category created successfully",
Data: &echo.Map{"data": updateResult},
})
}

return c.JSON(http.StatusConflict, responses.StatusResponse{
Status: http.StatusConflict,
Message: "Category already exists",
})
}

// Updates a category, e.g. singular -> plural
func UpdateCategory(c echo.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

categoryId := c.Param("categoryId")
_, err := primitive.ObjectIDFromHex(categoryId)
if err != nil {
return c.JSON(http.StatusBadRequest, responses.StatusResponse{
Status: http.StatusBadRequest,
Message: "Invalid category ID: " + err.Error(),
})
}

var category models.Category
if err := c.Bind(&category); err != nil {
return c.JSON(http.StatusBadRequest, responses.StatusResponse{
Status: http.StatusBadRequest,
Message: err.Error(),
})
}

update := bson.M{
"$set": bson.M{
"category_name": category.Category_name,
},
}

// Perform the Update operation
updateResult, err := categoriesCollection.UpdateOne(ctx, bson.M{"_id": categoryId}, update)

if err != nil {
return c.JSON(http.StatusInternalServerError, responses.StatusResponse{
Status: http.StatusInternalServerError,
Message: err.Error(),
})
}

return c.JSON(http.StatusOK, responses.StatusResponse{
Status: http.StatusOK,
Message: "Category updated successfully",
Data: &echo.Map{"data": updateResult},
})
}

func DeleteCategory(c echo.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

categoryId := c.Param("categoryId")
_, err := primitive.ObjectIDFromHex(categoryId)
if err != nil {
return c.JSON(http.StatusBadRequest, responses.StatusResponse{
Status: http.StatusBadRequest,
Message: "Invalid category ID: " + err.Error(),
})
}

_, err = categoriesCollection.DeleteOne(ctx, bson.M{"_id": categoryId})

if err != nil {
return c.JSON(http.StatusInternalServerError, responses.StatusResponse{
Status: http.StatusInternalServerError,
Message: err.Error(),
})
}

return c.JSON(http.StatusOK, responses.StatusResponse{
Status: http.StatusOK,
Message: "Category deleted successfully",
})
}
104 changes: 61 additions & 43 deletions peer-prep-be/src/controllers/question.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,55 @@ var validate = validator.New()
var errMessage = "error"
var successMessage = "success"

// Helper Functions
// Takes in the sortField and sortOrder from the query string and returns the FindOptions object
func ProcessSortParams(sortField string, sortOrder string) *options.FindOptions {
var findOptions *options.FindOptions

if sortField != "" {
order := 1 // Default to ascending order
if sortOrder == "desc" {
order = -1 // If 'desc' is provided, sort in descending order
}

// Set the sorting options
findOptions = options.Find().SetCollation(&options.Collation{Locale: "en_US"}).SetSort(bson.D{{Key: sortField, Value: order}})
} else {
// No sorting specified, natural MongoDB order
findOptions = options.Find()
}

return findOptions
}

func ProcessFilterParams(filterField string, filterValues string) bson.D {
filter := bson.D{{}}

if filterField != "" && filterValues != "" {
values := strings.Split(filterValues, ",")

if len(values) == 1 {
filter = bson.D{{Key: filterField, Value: values[0]}}
} else {
filterConditions := bson.A{}
for _, value := range values {
filterConditions = append(filterConditions, bson.D{{Key: filterField, Value: value}})
}

filter = bson.D{
{
Key: "$or",
Value: filterConditions,
},
}
}

}

return filter
}

// Services
func CreateQuestion(c echo.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var existingQuestion models.Question
Expand All @@ -46,8 +95,10 @@ func CreateQuestion(c echo.Context) error {
return c.JSON(http.StatusBadRequest, responses.StatusResponse{Status: http.StatusBadRequest, Message: errMessage, Data: &echo.Map{"data": "Question with the same title already exists."}})
}

newQuestionId := primitive.NewObjectID()

newQuestion := models.Question{
Question_id: primitive.NewObjectID(),
Question_id: newQuestionId,
Question_title: question.Question_title,
Question_description: question.Question_description,
Question_categories: question.Question_categories,
Expand All @@ -60,6 +111,7 @@ func CreateQuestion(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, responses.StatusResponse{Status: http.StatusInternalServerError, Message: errMessage, Data: &echo.Map{"data": err.Error()}})
}

result.InsertedID = newQuestionId
return c.JSON(http.StatusCreated, responses.StatusResponse{Status: http.StatusCreated, Message: successMessage, Data: &echo.Map{"data": result}})
}

Expand Down Expand Up @@ -226,49 +278,15 @@ func SearchQuestion(c echo.Context) error {
return c.JSON(http.StatusOK, responses.StatusResponse{Status: http.StatusOK, Message: successMessage, Data: &echo.Map{"data": questions}})
}

// Takes in the sortField and sortOrder from the query string and returns the FindOptions object
func ProcessSortParams(sortField string, sortOrder string) *options.FindOptions {
var findOptions *options.FindOptions

if sortField != "" {
order := 1 // Default to ascending order
if sortOrder == "desc" {
order = -1 // If 'desc' is provided, sort in descending order
}

// Set the sorting options
findOptions = options.Find().SetCollation(&options.Collation{Locale: "en_US"}).SetSort(bson.D{{Key: sortField, Value: order}})
} else {
// No sorting specified, natural MongoDB order
findOptions = options.Find()
}

return findOptions
}

func ProcessFilterParams(filterField string, filterValues string) bson.D {
filter := bson.D{{}}

if filterField != "" && filterValues != "" {
values := strings.Split(filterValues, ",")

if len(values) == 1 {
filter = bson.D{{Key: filterField, Value: values[0]}}
} else {
filterConditions := bson.A{}
for _, value := range values {
filterConditions = append(filterConditions, bson.D{{Key: filterField, Value: value}})
}

filter = bson.D{
{
Key: "$or",
Value: filterConditions,
},
}
}
func GetDistinctQuestionCategories(c echo.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Get the distinct categories from the questions collection
categories, err := questionCollection.Distinct(ctx, "question_categories", bson.M{})
if err != nil {
return c.JSON(http.StatusInternalServerError, responses.StatusResponse{Status: http.StatusInternalServerError, Message: errMessage, Data: &echo.Map{"data": err.Error()}})
}

return filter
return c.JSON(http.StatusOK, responses.StatusResponse{Status: http.StatusOK, Message: successMessage, Data: &echo.Map{"data": categories}})
}
8 changes: 8 additions & 0 deletions peer-prep-be/src/models/category_model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package models

import "go.mongodb.org/mongo-driver/bson/primitive"

type Category struct {
Category_id primitive.ObjectID `json:"category_id,omitempty"`
Category_name string `json:"category_name" validate:"required"`
}
13 changes: 13 additions & 0 deletions peer-prep-be/src/routes/categories_route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package routes

import (
"github.com/CS3219-AY2425S1/cs3219-ay2425s1-project-g01/peer-prep-be/src/controllers"
"github.com/labstack/echo/v4"
)

func CategoriesRoute(e *echo.Echo) {
e.GET("/categories", controllers.GetCategories)
e.PUT("/categories/:categoryId", controllers.UpdateCategory)
e.POST("/categories", controllers.CreateCategory)
e.DELETE("/categories/:categoryId", controllers.DeleteCategory)
}
5 changes: 3 additions & 2 deletions peer-prep-be/src/routes/question_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (

func QuestionRoute(e *echo.Echo) {
e.GET("/questions/:questionId", controllers.GetQuestion)
e.GET("/questions", controllers.GetQuestions)
e.GET("/questions", controllers.GetQuestions)
e.GET("/questions/search", controllers.SearchQuestion)
e.POST("/question", controllers.CreateQuestion)
e.PUT("/questions/:questionId", controllers.UpdateQuestion)
e.DELETE("/questions/:questionId", controllers.DeleteQuestion)
}
e.GET("/questions/categories", controllers.GetDistinctQuestionCategories)
}
1 change: 1 addition & 0 deletions peer-prep-be/src/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func main() {
}))

routes.QuestionRoute(e)
routes.CategoriesRoute(e)

e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
Expand Down
Loading