Skip to content

Commit

Permalink
Merge pull request #19 from CS3219-AY2425S1/feat/qn-service-api
Browse files Browse the repository at this point in the history
Add category collection
  • Loading branch information
smolegz authored Sep 28, 2024
2 parents 2832c6e + cb175f4 commit dc03986
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 45 deletions.
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("/questions", 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

0 comments on commit dc03986

Please sign in to comment.