GORM database colum with json data - postgresql

I am trying to write an email service, where I want to store some data related to email into a Postgres DB using gorm. I have a field which needs to be stored as a JSON blob, which in request passed as a JSON object. When I am trying to migrate, it errors keep saying unsupported type map. When manually add the DB, then run gorm, it doesn't write the row to the database.
I have seen some example where it's using postgres.Json as field types, but I want the field loaded from the request as map[string]string.
// Email : Base with injected fields `UUID`, `CreatedAt`, `UpdatedAt`
type Email struct {
gorm.Model
Status string `grom:"type:varchar(32);not null"`
TemplateID string `grom:"type:varchar(256)"`
ToEmai string `grom:"type:varchar(256);not null"`
FromEmail string `grom:"type:varchar(256);not null"`
ToName string `grom:"type:varchar(256);not null"`
FromName string `grom:"type:varchar(256);not null"`
Subject string `grom:"type:varchar(256)"`
Message string `grom:"type:varchar"`
DynamicData *map[string]string `grom:"type:json"`
}
this is my model.
then I do a gin request:
// SendEmail : sends an email
func SendEmail(c *gin.Context) {
body, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
log.Error("Error reading request body to get rate estimates")
}
var newEmail = models.Email{
Status: "PENDING",
}
jsonErr := json.Unmarshal(body, &newEmail)
if jsonErr != nil {
log.Error(jsonErr)
}
database.DB.Create(&newEmail)
defer c.Request.Body.Close()
err = newEmail.SendSendgridEmail()
if err != nil {
c.JSON(http.StatusBadRequest, err)
} else {
c.JSON(http.StatusOK, "Successfully sent email")
}
}
which then looks into this function
func (e Email) dynamicTemplateEmailBody() []byte {
newMail := mail.NewV3Mail()
emailFrom := mail.NewEmail(e.FromName, e.FromEmail)
newMail.SetFrom(emailFrom)
newMail.SetTemplateID(e.TemplateID)
p := mail.NewPersonalization()
tos := []*mail.Email{
mail.NewEmail(e.ToName, e.ToEmai),
}
p.AddTos(tos...)
if e.DynamicData != nil {
for key, value := range *e.DynamicData {
log.Infof("%s %s", key, value)
p.SetDynamicTemplateData(key, value)
}
}
newMail.AddPersonalizations(p)
return mail.GetRequestBody(newMail)
}
I would like to be able to run DB.AutoMigrate(&models.Email{}) and automatically migrate the objects, or and when I make a request to the endpoint, the row gets added to my email table.

Related

Skip binding ID but return it as a response

I have a Person struct as follows:
type Person struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id"`
HomeAddress Address `bson:"home_address" json:"home_address"`
Pets []*struct {
ID string `json:"id"`
Name string `json:"name"`
Species string `json:"species"`
} `json:"items"`
}
The person's ID is generated automatically in mongodb. A person may have pets with ID that is generated using UUID in the backend. So the JSON sent by user shouldn't include the ID of Person and ID of every pet.
I use this struct when receiving the json sent by the user, saving the data to my mongodb, and return it as a response in my handler as follows:
PersonPostResponse struct {
models.Person
ServerTime time.Time `json:"server_time"`
EditFlag bool `json:"edit_locked"`
}
// handler of POST /person
return func(c *gin.Context) {
newPerson := models.Person{}
if err := c.shouldBindJSON(&newPerson); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if _, err := (*repo).SavePersonData(c, &newPerson); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, PersonPostResponse{newPerson, time.Now(), false})
}
The problem is, when I try to send the JSON with Person ID of 24 chars, it is accepted and saved to the mongodb. I tried putting the suggestions from this link https://github.com/gin-gonic/gin/pull/1733
ID primitive.ObjectID `bson:"id,omitempty" json:"-" binding:"-"`
but the POST response doesn't include the ID of the newly created Person as a result.
How do I skip ID of Person and ID of pets when binding but return it as a response?
Is it not a good practice to use the same struct for 3 different purposes? In reality I have 17 JSON fields and I'm not sure if its good that I rewrite them 3 times.
This can be handled in several ways. One way is to split the structure to support different views of it:
// Use this variant as the Person without ID
type Person struct {
// All person fields, except the ID
}
// Use this variant for DB operations and when you need ID
type DBPerson struct {
ID primitive.ObjectId
Person `bson:",inline"`
}
Another and sometimes simpler way to deal with it is that after binding the user, manually clear the ID you received:
newPerson := models.Person{}
if err := c.shouldBindJSON(&newPerson); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
newPerson.ID=primitive.NilObjectID
Or, you can also given an error if it is nonempty.

How to load structs with multiple nested association

I am trying to load one to one and one to many associations for one of my object using GORM. I keep getting the following error when I am trying to run.
{
"errors": [
{
"message": "can't preload field UserProfiles for models.Renter",
"path": [
"users"
]
}
],
"data": null
}
Here is my code
type User struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
Email string `gorm:"not null;unique_index"`
Password string
FirstName *string
LastName *string
Renter Renter `gorm:"auto_preload"`
Rentee Rentee `gorm:"auto_preload"`
UserProfiles []UserProfile `gorm:"association_autocreate:false;association_autoupdate:false"`
Roles []Role `gorm:"many2many:user_roles;association_autocreate:false;association_autoupdate:false"`
Permissions []Permission `gorm:"many2many:user_permissions;association_autocreate:false;association_autoupdate:false"`
}
// UserProfile saves all the related OAuth Profiles
type UserProfile struct {
BaseModelSeq
Email string `gorm:"unique_index:idx_email_provider_external_user_id"`
UserID uuid.UUID `gorm:"not null;index"`
User User `gorm:"association_autocreate:false;association_autoupdate:false"`
Provider string `gorm:"not null;index;unique_index:idx_email_provider_external_user_id;default:'DB'"` // DB means database or no ExternalUserID
ExternalUserID string `gorm:"not null;index;unique_index:idx_email_provider_external_user_id"` // User ID
Name string
FirstName string
LastName string
AvatarURL string `gorm:"size:1024"`
Description string `gorm:"size:1024"`
}
type Renter struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
UserID uuid.UUID `gorm:"unique;not null;index"`
Verified bool
Properties []Property `gorm:"association_autocreate:false;association_autoupdate:false"`
Listings []Listing `gorm:"association_autocreate:false;association_autoupdate:false"`
}
type Rentee struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
UserID uuid.UUID `gorm:"unique;not null;index"`
Verified bool
Bookings []Booking `gorm:"association_autocreate:false;association_autoupdate:false"`
}
then I call this function
func userList(r *queryResolver, id *string) (*gql.Users, error) {
entity := consts.GetTableName(consts.EntityNames.Users)
whereID := "id = ?"
record := &gql.Users{}
dbRecords := []*dbm.User{}
tx := r.ORM.DB.Begin().Preload(consts.EntityNames.UserProfiles)
defer tx.RollbackUnlessCommitted()
if id != nil {
tx = tx.Where(whereID, *id)
}
tx = tx.Find(&dbRecords).Count(&record.Count)
for _, dbRec := range dbRecords {
renter := dbm.Renter{}
tx = tx.Model(&dbRec).Related(&renter)
logger.Infof("%+v", dbRec.Renter)
// rentee := dbm.Rentee{}
// tx.Related(&rentee)
// logger.Info(rentee)
if rec, err := tf.DBUserToGQLUser(dbRec); err != nil {
logger.Errorfn(entity, err)
} else {
record.List = append(record.List, rec)
}
}
return record, tx.Error
}
If I get rid of tx = tx.Model(&dbRec).Related(&renter) the query runs, the profile object loads but my renter and rentee object doesn't have the data from database. And I notice it doesn't run the query SELECT * FROM "renters" WHERE "renters"."deleted_at" IS NULL AND (("user_id" = 'my-user-uuid'))
I also tried to this this:
tx = tx.Preload(consts.EntityNames.Renters).Preload(consts.EntityNames.Rentees).Preload(consts.EntityNames.UserProfiles).Find(&dbRecords).Count(&record.Count)
but get thise error: can't preload field Renters for models.User

How to handle duplicate unique index error?

I'm using MongoDB. Code to add data to the collection:
type User struct {
Firstname string `json:"firstname" bson:"firstname"`
Lastname *string `json:"lastname,omitempty" bson:"lastname"`
Username string `json:"username" bson:"username"`
RegistrationDate primitive.DateTime `json:"registrationDate" bson:"registrationData"`
LastLogin primitive.DateTime `json:"lastLogin" bson:"lastLogin"`
}
var client *mongo.Client
func AddUser(response http.ResponseWriter, request *http.Request) {
collection := client.Database("hattip").Collection("user")
var user User
_ = json.NewDecoder(request.Body).Decode(&user)
insertResult, err := collection.InsertOne(context.TODO(), user)
if err != nil {
// here i need to get the kind of error.
fmt.Println("Error on inserting new user", err)
response.WriteHeader(http.StatusPreconditionFailed)
} else {
fmt.Println(insertResult.InsertedID)
response.WriteHeader(http.StatusCreated)
}
}
func main() {
client = GetClient()
err := client.Ping(context.Background(), readpref.Primary())
if err != nil {
log.Fatal("Couldn't connect to the database", err)
} else {
log.Println("Connected!")
}
router := mux.NewRouter()
router.HandleFunc("/person", AddUser).Methods("POST")
err = http.ListenAndServe("127.0.0.1:8080", router)
if err == nil {
fmt.Println("Server is listening...")
} else {
fmt.Println(err.Error())
}
}
func GetClient() *mongo.Client {
clientOptions := options.Client().ApplyURI("mongodb://127.0.0.1:27017")
client, err := mongo.NewClient(clientOptions)
if err != nil {
log.Fatal(err)
}
err = client.Connect(context.Background())
if err != nil {
log.Fatal(err)
}
return client
}
If I add a record with a username that already exists in the database, I get -
Error on inserting new user multiple write errors: [{write errors:
[{E11000 duplicate key error collection: hattip.user index:
username_unique dup key: { username: "dd" }}]}, {}]
in the line fmt.Println("Error on inserting new user", err) The record with the string dd in the username field is already there, and the username field is a unique index.
I want to be sure that the error is exact E11000 error (a repeating collection of key errors).
So far i compare err to whole error string that appears on duplication of a unique field, but it's completely wrong. If there is a way to get error code from err object, or there are other ways to solve this problem?
Also, i found mgo package, but to use it properly i have to learn it, rewrite current code and so on, but honestly, it looks good:
if mgo.IsDup(err) {
err = errors.New("Duplicate name exists")
}
According to the driver docs, InsertOne may return a WriteException, so you can check if the error is a WriteException, and if it is so, then check the WriteErrors in it. Each WriteError contains an error code.
if we, ok:=err.(WriteException); ok {
for _,e:=range we.WriteErrors {
// check e.Code
}
}
You can write an IsDup based on this.

Create new struct and append it to array in a one to many relationship

I'm trying to append a new item to an array in a one-to-many relationship. The problem is that one of the IDs is always undefined and the model I want to append to does not get updated.
I have the following models:
type Station struct {
gorm.Model
Name string
Measurements []Measurement
PlantID uint64
Plant Plant
}
type Measurement struct {
ID uint64 `gorm:"primary_key"`
StationID uint64
TempSensor float32
LightSensor float32
HumiditySensor float32
CreatedAt time.Time
}
type Plant struct {
gorm.Model
Name string
}
This is the route to which I'm sending the post request:
/stations/:id/measurements
This is the current route handler that I have:
func CreateMeasurement(c *gin.Context) {
id := c.Params.ByName("id")
var station Station
if err := db.Where("id = ?", id).First(&station).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
var measurement Measurement
c.BindJSON(&measurement)
// Convert params string to uint
convertedID, err := strconv.ParseUint(id, 10, 64)
if err != nil {
fmt.Println(err)
}
measurement.StationID = convertedID
db.Model(&station).Association("Measurements").Append(&measurement)
db.Save(&station)
c.JSON(200, station)
}
}
Question: How can I create a new Measurement item and append it to the []Measurement array in a specific Station which is specified by the route parameters?
Solved the problem. It turns out there was some problem with the database table. Although I got auto migrate enable, there was a problem with some ids that were null.
Here is the working route:
func CreateMeasurement(c *gin.Context) {
id := c.Params.ByName("id")
var station Station
if err := db.Where("id = ?", id).First(&station).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
var measurement Measurement
c.BindJSON(&measurement)
db.Model(&station).Association("Measurements").Append(&measurement)
c.JSON(200, station)
}
}

How to assign BSON _Id to a cookie (Go, Mongodb)

I was trying to create a go cookie. I want to assign Id from Mongodb to be stored in the Cookie. But while compiling I am getting an error as follows:-
"unknown http.Cookie field 'Id' in struct literal"
The following is my code:-
getUser := user.CheckDB()
expiration := time.Now().Add(365 * 24 * time.Hour)
//The Error is Caused by the Next Line
cookie := http.Cookie{Id: getUser[0].Id, Name: getUser[0].Email, Value: getUser[0].Password, Expires: expiration}
http.SetCookie(w, &cookie)
func (this *User) CheckDB() []User {
var results []User
sess, db := GetDatabase()
defer sess.Close()
c := db.C("user")
uname := &this.Email
err := c.Find(bson.M{"email": *uname}).Sort("-id").All(&results)
if err != nil {
panic(err)
} else {
fmt.Println("Results All: ", results)
return results
}
}
type Cookie struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string
}
Thanks in advance.
Here is a solution for this problem.
Cookie struct below:
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string
}
Cookie Creation
value := map[string]string{
"id": cookieId,
}
if encoded, err := ckieHandler.Encode("session", value); err == nil {
cookie := &http.Cookie{
Name: "session",
Value: encoded,
Path: "/",
}
http.SetCookie(response, cookie)
}
Cookie Call
if cookie, err := request.Cookie("session"); err == nil {
cookieValue := make(map[string]string)
if err = ckieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
id = cookieValue["id"] // **Pass BSON ID here**
}
}
For more details Click Here. This link helped me a lot. Hoping someone will find this answer useful.