mongodbgogo-fiber

Reusing model and removing fields from the response in Fiber/MongoDB


I'm trying not to create a wall of code and not re-declare code if not needed.

My two major issues right now are:

In line 47 there's overwriting/decoding on an existing user model userCollection.FindOne(ctx, filter, opts).Decode(&user) but it's not being updated, options from line 46 are not applied, unless I'll declare var user2 = models.User, and decode to user2 in line 47 then in line 49 returning user2

In line 46 there are opts := options.FindOne().SetProjection(bson.M{"password": 0}). If I'm working with second user user2 from the example above, it's returning the password in the JSON response but it's null. Is it possible to remove the password key completely from the response without creating another user model just to use it in the response?

func CreateUser(c *fiber.Ctx) error {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    var user models.User
 
    //validate the request body
    if err := c.BodyParser(&user); err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
 
    //use the validator library to validate required fields
    if validationErr := validate.Struct(&user); validationErr != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": validationErr.Error()}})
    }
    var email = &user.Email
 
    count, err := userCollection.CountDocuments(ctx, bson.M{"email": email})
    if err != nil {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": "Something went wrong"}})
    }
    if count > 0 {
        return c.Status(http.StatusBadRequest).JSON(responses.UserResponse{Status: http.StatusBadRequest, Message: "error", Data: &fiber.Map{"data": "Email already in use"}})
    }
 
    //set the status, hash password, set activate token and updated at
    status := 0
    password := hashPassword(*user.Password)
    activateToken := uuid.New().String()
    updatedAt, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))
 
    //create user object
    user.ID = primitive.NewObjectID()
    user.Password = &password
    user.Status = &status
    user.ResetToken = &activateToken
    user.CreatedAt = updatedAt
    user.UpdatedAt = updatedAt
 
    result, err := userCollection.InsertOne(ctx, user)
    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(responses.UserResponse{Status: http.StatusInternalServerError, Message: "error", Data: &fiber.Map{"data": err.Error()}})
    }
 
    //get created user from the DB and cast it into UserResponse model
    filter := bson.M{"_id": result.InsertedID}
    opts := options.FindOne().SetProjection(bson.M{"password": 0})
    userCollection.FindOne(ctx, filter, opts).Decode(&user)
    //return created user
    return c.Status(http.StatusOK).JSON(responses.UserResponse{Status: http.StatusOK, Message: "success", Data: &fiber.Map{"data": user}})
}

I've already tried to create a separate model UserResponse without a password field and declare second user model in the CreateUser function to be able to see the output of FindOne with options in the response.


Solution

  • After hours of figuring it out and posting it here, I've got a moment of brilliance.

    All that changes here is a re-declaring user as an empty User model:

        user = models.User{} // <- the fix
        filter := bson.M{"_id": result.InsertedID}
        opts := options.FindOne().SetProjection(bson.M{"password": 0})
        userCollection.FindOne(ctx, filter, opts).Decode(&user)