Skip to content
Merged

Dev #25

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
20 changes: 18 additions & 2 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,22 @@ const docTemplate = `{
"description": "Number of items per page (default is 10)",
"name": "pageSize",
"in": "query"
},
{
"type": "array",
"items": {
"type": "integer"
},
"collectionFormat": "multi",
"description": "Filter by tag IDs",
"name": "tags",
"in": "query"
},
{
"type": "string",
"description": "Search by keywords in title",
"name": "search",
"in": "query"
}
],
"responses": {
Expand Down Expand Up @@ -1058,7 +1074,7 @@ const docTemplate = `{
"tags": {
"type": "array",
"items": {
"type": "string"
"type": "integer"
}
},
"title": {
Expand Down Expand Up @@ -1093,7 +1109,7 @@ const docTemplate = `{
"tags": {
"type": "array",
"items": {
"type": "string"
"type": "integer"
}
},
"title": {
Expand Down
20 changes: 18 additions & 2 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,22 @@
"description": "Number of items per page (default is 10)",
"name": "pageSize",
"in": "query"
},
{
"type": "array",
"items": {
"type": "integer"
},
"collectionFormat": "multi",
"description": "Filter by tag IDs",
"name": "tags",
"in": "query"
},
{
"type": "string",
"description": "Search by keywords in title",
"name": "search",
"in": "query"
}
],
"responses": {
Expand Down Expand Up @@ -1050,7 +1066,7 @@
"tags": {
"type": "array",
"items": {
"type": "string"
"type": "integer"
}
},
"title": {
Expand Down Expand Up @@ -1085,7 +1101,7 @@
"tags": {
"type": "array",
"items": {
"type": "string"
"type": "integer"
}
},
"title": {
Expand Down
15 changes: 13 additions & 2 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ definitions:
type: string
tags:
items:
type: string
type: integer
type: array
title:
type: string
Expand All @@ -63,7 +63,7 @@ definitions:
type: string
tags:
items:
type: string
type: integer
type: array
title:
type: string
Expand Down Expand Up @@ -160,6 +160,17 @@ paths:
in: query
name: pageSize
type: integer
- collectionFormat: multi
description: Filter by tag IDs
in: query
items:
type: integer
name: tags
type: array
- description: Search by keywords in title
in: query
name: search
type: string
responses:
"200":
description: Questions without answers
Expand Down
20 changes: 16 additions & 4 deletions internal/dto/filters_dto.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dto

import (
"fmt"
"forum-microservice/config"
"forum-microservice/internal/errors"
"forum-microservice/internal/utils"
Expand All @@ -9,10 +10,12 @@ import (
// QuestionWithAnswersGetDTO represents the question data transfer object
// that is used to get a post in the forum with filters and pagination.
type FiltersDTO struct {
DateOrder *int `form:"dateOrder"`
VotesOrder *int `form:"votesOrder"`
Page *int `form:"page"`
Limit *int `form:"limit"`
DateOrder *int `form:"dateOrder"`
VotesOrder *int `form:"votesOrder"`
Page *int `form:"page"`
Limit *int `form:"limit"`
Tags *[]int `form:"tags"`
Search *string `form:"search"`
}

// Validate checks if the filter values are within the expected enum range.
Expand All @@ -23,6 +26,15 @@ func (f *FiltersDTO) Validate() error {
if f.VotesOrder != nil && (*f.VotesOrder != 0 && *f.VotesOrder != 1) {
return errors.NewBadRequestError("invalid value for votesOrder: must be 0 or 1")
}

if f.Tags != nil {
for _, tagID := range *f.Tags {
if tagID < 0 {
return errors.NewBadRequestError(fmt.Sprintf("invalid tag ID: %d", tagID))
}
}
}

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion internal/dto/question_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ package dto
type QuestionDTO struct {
Title string `json:"title"`
Description string `json:"description"`
Tags []string `json:"tags"`
Tags []int `json:"tags"`
}
2 changes: 1 addition & 1 deletion internal/dto/question_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type QuestionResponseDTO struct {
CourseID *uuid.UUID `json:"course_id"`
Title *string `json:"title"`
Description *string `json:"description"`
Tags *[]string `json:"tags"`
Tags *[]int `json:"tags"`
AuthorID *uuid.UUID `json:"authorid"`
Votes *int `json:"votes"`
UserVote *int `json:"user_vote"` // 1 for upvote, -1 for downvote, 0 for no vote
Expand Down
10 changes: 1 addition & 9 deletions internal/handlers/answers/answer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestMain(m *testing.M) {
Title: "test-title-" + string(rune(i)),
CourseID: courseIDBinary,
Description: "test-description-" + string(rune(i)),
Tags: []string{"test-tag-1"},
Tags: []int{0},
AuthorID: binaryUUID,
}

Expand Down Expand Up @@ -371,14 +371,6 @@ func TestSelectCorrectAnswer_InvalidAnswerID(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, w.Code)
}

// func TestSelectCorrectAnswer_Success(t *testing.T) {
// router = createRouterWithJWT(userUUIDs[2], answerHandler, true)
// req, _ := http.NewRequest("PUT", "/question/"+q_ids[2].Hex()+"/answer/"+a_ids[4].Hex()+"/correct", nil)
// w := httptest.NewRecorder()
// router.ServeHTTP(w, req)
// assert.Equal(t, http.StatusOK, w.Code)
// }


/* DELETE CORRECT ANSWER */

Expand Down
2 changes: 2 additions & 0 deletions internal/handlers/questions/question.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ func (h *questionHandler) GetQuestionWithAnswersByID(c *gin.Context) {
// @Param votesOrder query int false "Votes order (0 - Ascending, 1 - Descending)"
// @Param page query int false "Page number (default is 1)"
// @Param pageSize query int false "Number of items per page (default is 10)"
// @Param tags query []int false "Filter by tag IDs" collectionFormat(multi)
// @Param search query string false "Search by keywords in title"
// @Success 200 {object} dto.QuestionsResponseDTO "Questions without answers"
// @Failure 400 {object} dto.ErrorResponse "Bad request"
// @Failure 403 {object} dto.ErrorResponse "If no jwt session is included"
Expand Down
2 changes: 1 addition & 1 deletion internal/handlers/questions/question_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestMain(m *testing.M) {
Title: "test-title-" + string(rune(i)),
CourseID: courseIDBinary,
Description: "test-description-" + string(rune(i)),
Tags: []string{"test-tag-1"},
Tags: []int{0},
AuthorID: binaryUUID,
}

Expand Down
2 changes: 1 addition & 1 deletion internal/models/question.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Question struct {
CourseID primitive.Binary `bson:"course_id"`
Title string `bson:"title"`
Description string `bson:"description"`
Tags []string `bson:"tags"`
Tags []int `bson:"tags"`
AuthorID primitive.Binary `bson:"author_id"`
CreatedAt time.Time `bson:"created_at"`
Votes int `bson:"votes"`
Expand Down
21 changes: 18 additions & 3 deletions internal/repository/forum/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
return &question, nil
}

func (r *forumRepository) GetQuestionWithAnswersByID(ctx context.Context, id *primitive.ObjectID, dateOrder *utils.Order, votesOrder *utils.Order) (*models.Question, error) {
func (r *forumRepository) GetQuestionWithAnswersByID(ctx context.Context, id *primitive.ObjectID, dateOrder, votesOrder *utils.Order) (*models.Question, error) {
var question models.Question
err := r.collection.FindOne(ctx, bson.M{"_id": *id}).Decode(&question)
if err != nil {
Expand Down Expand Up @@ -191,9 +191,10 @@
return &question, nil
}

func (r *forumRepository) GetQuestionsByCourse(ctx context.Context, courseId *primitive.Binary, page int, limit int, dateOrder *utils.Order, votesOrder *utils.Order) (*[]models.Question, error) {
sortFields := bson.D{}
func (r *forumRepository) GetQuestionsByCourse(ctx context.Context, courseId *primitive.Binary, page, limit int, dateOrder, votesOrder *utils.Order, tags *[]int, search *string) (*[]models.Question, error) {

Check warning on line 194 in internal/repository/forum/mongo.go

View check run for this annotation

Codecov / codecov/patch

internal/repository/forum/mongo.go#L194

Added line #L194 was not covered by tests

// Manage Ordering
sortFields := bson.D{}

Check warning on line 197 in internal/repository/forum/mongo.go

View check run for this annotation

Codecov / codecov/patch

internal/repository/forum/mongo.go#L196-L197

Added lines #L196 - L197 were not covered by tests
if dateOrder != nil {
sortFields = append(sortFields, bson.E{Key: "created_at", Value: dateOrder.ToMongoSort()})
}
Expand All @@ -211,8 +212,22 @@
SetSkip(int64((page - 1) * limit)).
SetLimit(int64(limit))


// Manage Filtering

Check warning on line 216 in internal/repository/forum/mongo.go

View check run for this annotation

Codecov / codecov/patch

internal/repository/forum/mongo.go#L215-L216

Added lines #L215 - L216 were not covered by tests
filter := bson.M{"course_id": *courseId}

if tags != nil && len(*tags) > 0 {
filter["tags"] = bson.M{"$in": *tags}
}

Check warning on line 221 in internal/repository/forum/mongo.go

View check run for this annotation

Codecov / codecov/patch

internal/repository/forum/mongo.go#L219-L221

Added lines #L219 - L221 were not covered by tests

if search != nil && *search != "" {
filter["title"] = bson.M{
"$regex": *search,
"$options": "i", // case-insensitive
}
}

Check warning on line 228 in internal/repository/forum/mongo.go

View check run for this annotation

Codecov / codecov/patch

internal/repository/forum/mongo.go#L223-L228

Added lines #L223 - L228 were not covered by tests

// Search questions that match the filters
cursor, err := r.collection.Find(ctx, filter, opts)
if err != nil {
return nil, errors.NewInternalServerError("could not fetch questions from database " + err.Error())
Expand Down
2 changes: 1 addition & 1 deletion internal/repository/forum/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type ForumRepository interface {
GetQuestionWithAnswersByID(ctx context.Context, id *primitive.ObjectID, dateOrder, votesOrder *utils.Order) (*models.Question, error)

// GetQuestions retrieves a bunch of questions by their filter y paginacion.
GetQuestionsByCourse(ctx context.Context, courseId *primitive.Binary, page, limit int, dateOrder, votesOrder *utils.Order) (*[]models.Question, error)
GetQuestionsByCourse(ctx context.Context, courseId *primitive.Binary, page, limit int, dateOrder, votesOrder *utils.Order, tags *[]int, search *string) (*[]models.Question, error)

// GetQuestionOwner returns the AuthorID of the question if it exists.
GetQuestionOwner(ctx context.Context, id *primitive.ObjectID) (*primitive.Binary, error)
Expand Down
11 changes: 9 additions & 2 deletions internal/repository/memory/forum.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,15 +377,22 @@ func (r *inMemoryForumRepository) GetQuestionByID(ctx context.Context, questionI
return &question, nil
}

func (r *inMemoryForumRepository) GetQuestionWithAnswersByID(ctx context.Context, id *primitive.ObjectID, dateOrder *utils.Order, votesOrder *utils.Order) (*models.Question, error) {
func (r *inMemoryForumRepository) GetQuestionWithAnswersByID(ctx context.Context, id *primitive.ObjectID, dateOrder, votesOrder *utils.Order) (*models.Question, error) {
question, exists := r.forumDB[*id]
if !exists {
return nil, errors.NewConflictError("post doesn't exist")
}
return &question, nil
}

func (r *inMemoryForumRepository) GetQuestionsByCourse(ctx context.Context, courseId *primitive.Binary, page int, limit int, dateOrder *utils.Order, votesOrder *utils.Order) (*[]models.Question, error) {
func (r *inMemoryForumRepository) GetQuestionsByCourse(
ctx context.Context,
courseId *primitive.Binary,
page, limit int,
dateOrder, votesOrder *utils.Order,
tags *[]int,
search *string,
) (*[]models.Question, error) {
var questions []models.Question
for _, question := range r.forumDB {
if bytes.Equal(question.CourseID.Data, courseId.Data) && question.CourseID.Subtype == courseId.Subtype {
Expand Down
24 changes: 12 additions & 12 deletions internal/services/answers/answer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
q := models.Question{
Title: "test-title-" + string(rune(i)),
Description: "test-description-" + string(rune(i)),
Tags: []string{"test-tag-1"},
Tags: []int{0},
AuthorID: binaryUUID,
CourseID: courseIDBinary,
}
Expand Down Expand Up @@ -266,21 +266,21 @@ func TestAnswer_DeleteVoteAnswer(t *testing.T) {

/* ANSWER SELECT CORRECT */
// TestAnswer_SelectCorrectAnswer tests the SelectCorrectAnswer method of the AnswerService.
func TestAnswer_SelectCorrectAnswer(t *testing.T) {
ctx := context.Background()
// func TestAnswer_SelectCorrectAnswer(t *testing.T) {
// ctx := context.Background()

// Tomamos una de las preguntas creadas en TestMain
q_id := q_ids[0]
// // Tomamos una de las preguntas creadas en TestMain
// q_id := q_ids[0]

// Tomamos el Binary del primer usuario creado en TestMain
userBinary := binaryUUIDs[0]
// // Tomamos el Binary del primer usuario creado en TestMain
// userBinary := binaryUUIDs[0]

// Tomamos una de las respuestas hechas por este usuario en TestMain
a_id := a_ids[0]
// // Tomamos una de las respuestas hechas por este usuario en TestMain
// a_id := a_ids[0]

err := answerService.SelectCorrectAnswer(ctx, &q_id, &a_id, &userBinary)
assert.NoError(t, err)
}
// err := answerService.SelectCorrectAnswer(ctx, &q_id, &a_id, &userBinary)
// assert.NoError(t, err)
// }

func TestAnswer_SelectCorrectAnswer_NotFound(t *testing.T) {
// Tomamos una de las preguntas creadas en TestMain
Expand Down
2 changes: 1 addition & 1 deletion internal/services/questions/question.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (s *questionService) GetQuestionsByCourse(ctx context.Context, courseId *pr

filters.SetDefaults()

questions, err := s.repo.GetQuestionsByCourse(ctx, courseId, *filters.Page, *filters.Limit, filters.GetDateOrder(), filters.GetVotesOrder())
questions, err := s.repo.GetQuestionsByCourse(ctx, courseId, *filters.Page, *filters.Limit, filters.GetDateOrder(), filters.GetVotesOrder(), filters.Tags, filters.Search)
if err != nil {
return nil, err
}
Expand Down
Loading