I have two models
type Role struct {
models.BaseModel
UID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();uniqueIndex:idx_uniqueGroupId" form:"id" xml:"id" json:"id"`
Name string `gorm:"size:150;uniqueIndex:idx_uniqueGroup" form:"name" xml:"name" json:"name"`
Permission []Permission `gorm:"many2many:roles_permissions;constraint:OnDelete:CASCADE;" form:"rolePermissions" xml:"rolePermissions" json:"rolePermissions"`
}
and
type Permission struct {
models.BaseModel
UID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();uniqueIndex:idx_uniquePermId" form:"id" xml:"id" json:"id"`
Model string `gorm:"uniqueIndex:idx_uniquePerm" form:"entity" xml:"entity" json:"entity"`
Name string `gromr:"uniqueIndex:idx_uniquePerm" form:"name" xml:"name" json:"name"`
}
I created a function which will return role with related permissions as
func GetRoleByUid(uid uuid.UUID, db *gorm.DB) (*Role, error) {
var role Role
if err := db.Model(&Role{}).Preload("Permissions").Find(&role, "uid = ?", uid).Error ; err != nil {
return nil, err
}
if role.UID == uuid.Nil {
return nil, errors.New("Role not Found")
}
return &role, nil
}
But i got an error "Permissions: unsupported relations for schema Role"
How can I resolve this ?
Related
Two structs Thread & Interest have a many to many relationship with each other. According to the documentation this is how both structs looks like:
type Thread struct {
ID uuid.UUID `gorm:"primaryKey" json:"threadId"`
Title string `gorm:"not null" json:"title"`
Body string `gorm:"not null" json:"body"`
CreatedAt time.Time
UpdatedAt time.Time
// Foreign keys
UserID uuid.UUID `json:"userId"`
// Has many association
Comments []Comment `json:"comments"`
// many to many associations
Interests []Interest `gorm:"many2many:thread_interests;" json:"interests"`
UsersLiked []User `gorm:"many2many:post_likes;" json:"usersLiked"`
UsersDisliked []User `gorm:"many2many:post_dislikes;" json:"usersDisliked"`
}
type Interest struct {
ID uuid.UUID `gorm:"primaryKey" json:"interestId"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
// Many to many associations
UserProfiles []UserProfile `gorm:"many2many:user_interests;" json:"userProfiles"`
Threads []Thread `gorm:"many2many:thread_interests;" json:"threads"`
}
Since it is many to many between both structs I created another struct to act as join table named ThreadInterest. The code:
type ThreadInterest struct {
ThreadID uuid.UUID `gorm:"primaryKey;"`
InterestID uuid.UUID `gorm:"primaryKey;"`
CreatedAt time.Time
UpdatedAt time.Time
}
The problem is that whenever I create a new thread with the corresponding interests it creates the thread record but no new record is created in the join table ThreadInterest. I tried reading the documentation and searching online but I can't find a solution. I hope you guys could help me on this.
UPDATE:
Here is how I create the thread
handler:
func HandleCreateThread(context *gin.Context) {
var threadInput apimodels.ThreadInput
err := context.ShouldBindJSON(&threadInput)
if err != nil {
// return error
context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userId, err := api.GetTokenUserId(context)
if err != nil {
// return error
context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var interests []databasemodels.Interest
interests, err = dataaccess.FindInterestByIds(threadInput.Interests)
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
thread := databasemodels.Thread{
Title: threadInput.Title,
Body: threadInput.Body,
Interests: interests,
UserID: userId,
}
newThread, err := dataaccess.CreateNewThread(&thread)
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
context.JSON(http.StatusOK, gin.H{"thread": newThread})
}
dataaccess:
func CreateNewThread(thread *databasemodels.Thread) (*databasemodels.Thread, error) {
// Generate ID and hash password
thread.ID = api.GenerateUUID()
// store to database
err := database.Database.Create(&thread).Error
if err != nil {
return &(databasemodels.Thread{}), err
}
return thread, nil
}
And this is how I migrate my database:
var Database *gorm.DB
// start a connection to the specified postgresql db from env file
func ConnectDb() {
var err error
host := os.Getenv("DB_HOST")
username := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
databaseName := os.Getenv("DB_NAME")
port := os.Getenv("DB_PORT")
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Africa/Lagos", host, username, password, databaseName, port)
Database, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
} else {
fmt.Println("Successfully connected to the database")
}
}
// conducts migrations to the connected database (built-in GORM functionality)
func AutoMigrateDb() {
Database.AutoMigrate(
&(databasemodels.User{}),
&(databasemodels.UserProfile{}),
&(databasemodels.Interest{}),
&(databasemodels.UserInterest{}),
&(databasemodels.Comment{}),
&(databasemodels.Thread{}),
&(databasemodels.ThreadInterest{}),
&(databasemodels.PostLike{}),
&(databasemodels.PostDislike{}),
&(databasemodels.CommentLike{}),
&(databasemodels.CommentDislike{}),
)
}
I have this model.
// Incident is a security incident.
type Incident struct {
ID string `json:"id" bson:"_id"`
Title string `json:"title" bson:"title"`
Description string `json:"description" bson:"description"`
Issue *Issue `json:"issue" bson:"issue"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
ModifiedAt time.Time `json:"modified_at" bson:"modified_at"`
}
// Issue is a security issue.
type Issue struct {
ID string `json:"id" bson:"_id"`
Title string `json:"title" bson:"title"`
IncidentID string `json:"incident_id"`
Description string `json:"description" bson:"description"`
}
Each Incident has one Issue.
When I insert an Incident into Postgres, I only add the issue_id.
type incident struct {
ID string `db:"id"`
CustomerID string `db:"customer_id"`
InternalID string `db:"internal_id"`
Title string `db:"title"`
IssueID string `db:"issue_id"`
Description string `db:"description"`
CreatedAt time.Time `db:"created_at"`
ModifiedAt time.Time `db:"modified_at"`
}
func toIncident(model *models.Incident) (*incident, error) {
// Create the SQL
incident := &sqlIncident{
ID: model.ID,
CustomerID: model.CustomerID,
InternalID: model.InternalID,
Title: model.Title,
Description: model.Description,
IssueID: "",
CreatedAt: model.CreatedAt,
ModifiedAt: model.ModifiedAt,
}
// Add the issue ID
if model.Issue != nil {
incident.IssueID = model.Issue.ID
}
return incident, nil
}
I would like to be able to do the reverse operation with a JOIN on the Issue when I get() or list() incidents.
I am using "github.com/Masterminds/squirrel" and "github.com/jmoiron/sqlx".
This code below would be the code to get an Incident.
// Prepare query
query := squirrel.Select(*).
From(r.GetTableName()).
PlaceholderFormat(squirrel.Dollar).
Join("????????")
// Build the SQL query
q, args, err := query.ToSql()
if err != nil {
return fmt.Errorf("postgresql: unable to build query: %w", err)
}
// Get the session repo
session := r.GetSession().(*sqlx.DB)
// Prepare the statement
stmt, err := session.PreparexContext(ctx, q)
if err != nil {
return fmt.Errorf("postgresql: unable to prepapre query: %w", err)
}
// Do the query
err = stmt.SelectContext(ctx, result, args...)
if err == sql.ErrNoRows {
return repositories.ErrNoResult
} else if err != nil {
return fmt.Errorf("postgresql: unable to execute query: %w", err)
}
How can I perform this correctly please ?
I am feeling that I am doing this wrong with the issue_id field that seems to be useless, and that I should add the definition of the Issue in my incident SQL structure, something like this below.
type incident struct {
ID string `db:"id"`
CustomerID string `db:"customer_id"`
InternalID string `db:"internal_id"`
Title string `db:"title"`
Issue issue `db:"issue"`
Description string `db:"description"`
CreatedAt time.Time `db:"created_at"`
ModifiedAt time.Time `db:"modified_at"`
}
But I can't see the next steps.
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.
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
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.