Strange Request Body in Golang API - mongodb

I have created an API server using golang and ravel. In one of the POST methods, I need to read the body and decode it to a model before saving it. But it is failing to do so. This is the guide I used https://medium.com/#kyawmyintthein/revel-mgo-restful-generator-for-revel-web-framework-mongodb-86209de3977e
The expected behaviour is to create the user object in the mongoDB. But I am getting an error response. Something is going wrong while Decoding it to user struct.
Controller method:
func (c UserController) Create() revel.Result {
fmt.Print("Body: ")
fmt.Println(c.Request.Body)
var (
user models.User
err error
)
err = json.NewDecoder(c.Request.Body).Decode(&user)
if err != nil {
errResp := buildErrResponse(err, "403")
c.Response.Status = 403
return c.RenderJSON(errResp)
}
user, err = models.AddUser(user)
if err != nil {
errResp := buildErrResponse(err, "500")
c.Response.Status = 500
return c.RenderJSON(errResp)
}
c.Response.Status = 201
return c.RenderJSON(user)
}
User Model:
package models
import (
"gopkg.in/mgo.v2/bson"
"time"
"userAPI/app/models/mongodb"
)
type User struct {
ID bson.ObjectId `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"email"`
Phone string `json:"phone" bson:"phone"`
Username string `json:"username" bson:"username"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
}
JSON body used in POST request
{
"name":"kanishka",
"email":"kanishka#gmail.com",
"phone":"91238901238",
"username":"k"
}
This is the response I am getting
{
"error_code": "403",
"error_message": "EOF"
}
The output of println(c.Request.Body) on top of the controller method shows
&{0xc4202ffa80 false true {0 0} true false false
0x12a1430}
This is my first attempt at golang. Kindly help me to proceed further.

After searching, I found this issue #1011. Quote to the response:
Revel automatically calls ParseForm, ParseMultipartForm or whatever function is needed for a request, so req.Body is parsed and all valuable information is stored to controller.Params.(Fixed, Route, Query, Form, Files).
It means that, before calling your controller's method, the request body already being read by revel, so when you try to read it again the result will be EOF. From the docs, instead of using json.Decoder and request body, try the following:
c.Params.BindJSON(&user)

Related

Merge two MongoDB databases from old driver to official driver

I am using go and I need to merge these two databases to keep the clients happy. I have changed the go code to use the official driver but it refuses to accept time.Time.
So in short, I need to transfer this model database
MgoResults struct {
ID bson.ObjectId `bson:"_id"`
ZipID string `bson:"zid"`
Message string `bson:"message"`
Result string `bson:"res"`
Error error `bson:"errors"`
ExpirationDate time.Time `bson:"expirationDate"`
}
into this model database
MgoResults struct {
ID primitive.ObjectID `bson:"_id"`
ZipID string `bson:"zid"`
Message string `bson:"message"`
Result string `bson:"res"`
Error error `bson:"errors"`
ExpirationDate primitive.DateTime `bson:"expirationDate"`
}
I don't have any strategies yet, but I welcome any examples with go code.
I have thought about iterating through all items and changing types then saving it to new model. Both databases work perfectly.
My main problem is during decoding and rebuilding the struct. I am probably doing something wrong.
var v bson.M
err = result.Decode(&v)
if err != nil {
log.Println("error at decode result : ", err)
}
//start filling the struct
res.ZipID = v["zid"].(string)
if v["errors"] == nil{
res.Error = nil
}else{
res.Error = v["errors"].(error)
}
res.Message= v["message"].(string)
res.ID = v["_id"].(primitive.ObjectID)
res.Result = v["res"].(string)
if v["expirationDate"] == nil{
//res.ExpirationDate = time.Now()
}else{
res.ExpirationDate = v["expirationDate"].(time.Time )
//res.ExpirationDate = v["expirationDate"].(primitive.DateTime)
}
Thanks to #icza and this slightly related answer here: https://stackoverflow.com/a/64419437/2912740 .
I was able to modify my code a bit and it worked. So it looks like mongo returns its own time that needs some conversion to regular time.
mytime := v["expirationDate"].(primitive.DateTime)
res.ExpirationDate = mytime.Time()

MongoDB does not save timestamps

I need to save the timestamps for createdAt and updatedAt properties in the database, but they are not being saved automatically, they are being saved as {T: 0, I: 0}. I am using Mongo Driver to perform CRUD operations.
So, this leds me to another problem while attaching the current time to the user model; I read somewhere that both createdAt and updatedAt have to be primitive.Timestamp, but I don't know how to really save them.
I have tried User.CreatedAt = with:
time.Now()
new primitive.Timestamp(time.Now(), 1))
primitive.Timestamp{ T: uint32(time.Now().Unix()), I: 0 } (this seems to be working)
Getting back to the main problem, the best solution should be that the database allows me to configure the timestamps to be saved automatically whenever a insertOne() is triggered.
That could work always that assigning primitive.Timestamp to User.CreatedAt is correct.
So, this is my model:
package models
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
type User struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
Email string `bson:"email" json:"email"`
CreatedAt primitive.Timestamp `bson:"createdAt" json:"createdAt"`
UpdatedAt primitive.Timestamp `bson:"updatedAt" json:"updatedAt"`
}
And this is my service:
func CreateUser(user models.User) (models.User, error) {
db := database.GetDatabase()
result, err := db.Collection(collection).InsertOne(context.TODO(), user)
if err != nil {
return models.User{}, err
}
user.Id = result.InsertedID.(primitive.ObjectID)
user.CreatedAt = primitive.Timestamp{ T: uint32(time.Now().Unix()), I: 0 }
return user, nil
}
So, it is ok to manage the timestamps like this or did I just mistaken?
You can simply use time.Time:
type User struct {
Id primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
Email string `bson:"email" json:"email"`
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
}
MongoDB does not update these for you. You have to set them yourself before storing them in the DB:
user.CreatedAt=time.Now()
user.UpdatedAd=user.CreatedAt
result, err := db.Collection(collection).InsertOne(context.TODO(), user)

Fail to decode mongo document ID onto struct field

Trying to use mongo go driver to decode a fetch a single user document and decode it into a struct, using the findOne method. But, I am unable to decode the document ID on the struct field. I tried looking for it in their examples, or at other sites/blogs, but no luck. I am working with:
go v1.13.4
mongo v4.2.1
mongo-go-driver v1.1.3
Below is the code snippet:
type User struct {
ID interface{} `json:"_id"`
Name string
Email string
Password string // hashed
}
/* Other versions of User struct which I already tried
type User struct {
ID interface{}
Name string
Email string
Password string // hashed
}
type User struct {
ID string `json:"_id"`
Name string
Email string
Password string // hashed
}
type User struct {
ID string
Name string
Email string
Password string // hashed
}
*/
func main() {
conn := service.MongoConn() // get a mongo connection on the required database
user := &service.User{}
err := conn.Collection("users").
FindOne(context.Background(), bson.M{"email": "foo#bar.com"}).
Decode(user)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", user)
}
I want to use doc ID as a reference in other documents in a different collection, otherwise, I have to resort to some other unique field like email.
the struct should be like this:
import "go.mongodb.org/mongo-driver/bson/primitive"
type User struct {
ID primitive.ObjectID `bson:"_id"`
...
}
to transfer your _id to string please use xx.ID.Hex()
see more on Github

go.mongodb.org/mongo-driver - InsertOne with NilValueObjectId

I have the following struct
type Account struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Email string `json:"email"`
Password string `json:"password"`
}
and the following function
func (a *Account) Create() map[string]interface{} {
if resp, ok := a.Validate(); !ok {
return resp
}
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(a.Password), bcrypt.DefaultCost)
a.Password = string(hashedPassword)
users := db.Collection("users")
insertResult, err := users.InsertOne(context.TODO(), a)
if err != nil {
return utils.Message(false, "Error inserting user document "+err.Error())
}
... more code down hre
}
The problem I am having is that I can insert the first account, but any account after that can't be inserted due to a dup key error on the _id field. I know that mongoDB will auto generate an _id field and will use the one provided if there is an id. In my case in this create function, the a.ID (_id) is always be NilValue "_id" : ObjectId("000000000000000000000000")
Is there any way mongoDB can generate an _id for me even if I provide and ID field with the nil value?
I need the Account.ID `bson: "_id"` property on there so I can decode it when I am reading from mongoDB such as
func GetUser(email string) *Account {
account := &Account{}
users := db.Collection("users")
filter := bson.D{{"email", email}}
if err := users.FindOne(context.TODO(), filter).Decode(&account); err != nil {
return utils.Message(false, "Error Retrieving account for "+email)
}
// account.ID will be available due to the bson tag
}
I'd appreciate some feedback if I am doing this wrong and how I could do this better.
Thanks!
I found the problem. I did not add ```omitempty` to the bson tag.
It should be
type Account struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Email string `json:"email"`
Password string `json:"password"`
}

REST API + MongoDb ISODate

My GoLang struct:
type myPojo struct {
ID bson.ObjectId `json:"id" bson:"_id,omitempty"`
Start time.Time `json:"start"`
}
POST API JSON input request:
{
"Start":ISODate("2013-10-01T00:00:00.000Z")
}
My code to convert input JSON request to Golang Struct:
func myPostApi(w http.ResponseWriter, r *http.Request, db mongoDB) {
w.Header().Set("Content-Type", "application/json")
decoder := json.NewDecoder(r.Body)
var inputObj myPojo
err := decoder.Decode(&inputObj)
if err != nil {
//This gets executed
log.Println("Error occurred converting POST input json to myPojo data.")
log.Println(err)
}
}
Above code is not able to convert, and it goes inside error if block and prints below, please help.
2018/02/25 22:12:44 Error occurred converting POST input json to myPojo data.
2018/02/25 22:12:44 invalid character 'I' looking for beginning of value
The
ISODate("2013...")
value is not valid JSON. That looks like a symbol or function call, neither of which are allowed in JSON. And there is no date type in JSON:
The "right" JSON date format