Referencing GORM auto-generated fields in Go - go

I'm writing my first API, so bear with me. I am using Go, Postgres and GORM and a slew of other things I'm still picking up but I ran into an issue with GORM's AutoMigrate.
Initially my User struct looked like this:
type User struct {
gorm.Model
Email string `gorm:"unique" json:"email"`
Password string `json:"password"`
}
And when I ran db.AutoMigrate(&User{}) It auto-generated an id field in my User table (along with several date fields), which I wanted. What I am hung up on is figuring out how to reference these fields in my app. I have modified my User struct to now look like this:
type User struct {
gorm.Model
ID int `gorm:"primary_key" json:"id"`
Email string `gorm:"unique" json:"email"`
Password string `json:"password"`
}
But instead of linking the two id fields, when I access the stored user object as shown:
user := model.User{}
if err := db.First(&user, model.User{Email: email}).Error; err != nil {
respondError(w, http.StatusNotFound, err.Error())
return nil
}
there are now two distinct fields, the auto-generated and my own:
{
"ID": 2,
"CreatedAt": "2018-04-28T21:14:20.828547-04:00",
"UpdatedAt": "2018-04-28T21:14:20.828547-04:00",
"DeletedAt": null,
"id": 0,
"email": "joeynelson#gmail.com",
"password": <hash>
}
I realize the answer is likely right in front of my face, there must be a way to reference these auto-generated fields, right?

gorm.Model is a struct including some basic fields, which including fields ID, CreatedAt, UpdatedAt, DeletedAt.
Reference: http://gorm.io/docs/conventions.html#gorm-Model

Related

GO: import a struct and rename it in json

I have built a database in go with gorm. For this I created a struct and with this struct I created a table. So far so good. In the backend everything works, but in the frontend the problem is that the JSON which is called always returns the ID in upper case and swagger generates me an ID which is lower case. Is there a way in Go that I can overwrite the imported struct from gorm with a JSON identifier?
import "gorm.io/gorm"
type Report struct {
gorm.Model
CreatedBy User `gorm:"foreignKey:CreatedByUserID" json:"createdBy"`
Archived bool `json:"archived"`
}
This Struct gives me the following response
{
"ID": 8,
"CreatedAt": "2022-11-15T20:45:16.83+01:00",
"UpdatedAt": "2022-12-27T21:34:17.871+01:00",
"DeletedAt": null
"createdBy": {
"ID": 1,
"CreatedAt": "2022-11-15T20:02:17.497+01:00",
"UpdatedAt": "2022-11-15T20:02:17.497+01:00",
...
},
"archived": true,
}
Is there a way to make the ID lowercase (like Archived)? Or can I adjust it at swaggo so that it is generated in upper case.
What I have seen is that you can make the table without this gorm.Model and define all the attributes yourself. The problem is that I then have to create all the functionalities (delete, update, index, primary key, ...) of these columns myself.
You can use the mapstructure package.
mapstructure is a Go library for decoding generic map values to structures and vice versa
If you want to embed ID field from gorm.Model with custom json tag, and you want it to be on the same struct and not in a "substruct", you can use mapstructure:",squash" tag on the embedded model.
type Model struct {
ID uint `json:"id"`
}
type Report struct {
Model `mapstructure:",squash"`
Archived bool `json:"archived"`
}
func main() {
input := map[string]interface{}{
"id": 1,
"archived": false,
}
var report Report
if err := mapstructure.Decode(input, &report); err != nil {
panic(err)
}
fmt.Println("Report ID:", report.ID)
fmt.Println("Report ID via Model:", report.Model.ID)
}
As you can observe, with mapstructure.Decode method you can convert map to struct with the squash option, and you can then access ID of report directly. Note that you can still access report.Model and all its fields.
With mapstructure, you can make the ID lowercase as you wanted, and also accessable from the report struct directly, not only from a Model substruct.
I create my own gorm-model-struct:
type GormModel struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
} //#name models.gormModel
I import this struct into the other structs:
type Report struct {
GormModel
CreatedBy User `gorm:"foreignKey:CreatedByUserID" json:"createdBy"`
Archived bool `json:"archived"`
}
Important is, that you add the json-key and set the attribute name.

How can I use a struct in a gorm model struct without it being a model

I have a model struct
type Customer struct {
gorm.Model
Person `gorm:"-" json:"person"`
Contact `gorm:"-" json:"contact"`
Address `gorm:"-" json:"address"`
}
func (p Customer) Validate() error {
return validation.ValidateStruct(&p,
validation.Field(&p.Person),
validation.Field(&p.Contact),
validation.Field(&p.Address),
)
}
I want the customer to have Contact data so I have a contact struct. But whenever I try to run the server
type Contact struct {
Tel string `json:"tel"`
Mail string `json:"mail"`
URL string `json:"url"`
}
func (c Contact) Validate() error {
return validation.ValidateStruct(&c,
validation.Field(&c.Tel, validation.Required, is.Digit),
validation.Field(&c.Mail, validation.Required, is.Email),
validation.Field(&c.URL, is.URL),
)
}
I get
model.Customer's field Contact, need to define a foreign key for relations or it need to implement the Valuer/Scanner interface
But I don't want it to be on it's own seperate table. So how do I prevent that? I tried using
`gorm:"-"`
But if I then read the record as json all the values are empty
"contact": {
"tel": "",
"mail": "",
"url": ""
},
So my question is why do I need the scanner and valuer or a foreign key if I don't want it to be on it's own seperate table?
I had to use gorm:"embedded" instead of gorm:"-" as described in the docs
https://gorm.io/docs/models.html#Embedded-Struct

Multi value list foreign key sql gorm

type Users struct {
ID int64
Email string
Permissions string
}
type UserPermissions struct {
ID int64
Description json.RawMessage
}
The user json should be like this:
{
"status": 200,
"data": {
"id": 1,
"email": "hello#hello.com",
"permisions": [{
"id":"1",
"description":"Create permission"
},
{
"id":"3",
"description":"Edit permission"
}]
}
}
I have the following string in the Permissions of my User:
';1;3;5;7;' every number is the id related to the UserPermissions struct/table
How can I match the string with the user permission table using gorm?.
I'm using mysql
An answer to your question (if you are using PostgreSQL as your DB).
If you want to query for the Permissions that a given User record has, given its ID
perms := []Permission{}
err := db.Joins("JOIN users u ON id::string IN STRING_TO_ARRAY(TRIM(';' FROM u.permissions), ';')").
Where("u.id = ?", userID).
Find(&perms).
Error
if err != nil {
log.Fatal(err) // or something like that
}
As you can see, we are doing a funky join with functions that clean and split the permission field into individual string IDs. This is clunky, brittle and super slow and convoluted, and stems from the faulty relational design that you started with.
EDIT: Nevermind, for MySQL the equivalent solution is very much more complicated, so I suggest you instead use the advice below.
A better way
If you have control over the database design, the better way to do this would be
by not storing an array of IDs as a string, never a good thing in database design, but instead using a foreign key on the many side of the HasMany relationship*.
This would look like this as go structs:
type User struct {
ID int64 `json:"id"`
Email string `json:"email"`
Permissions []UserPermission `json:"permissions"`
}
type UserPermissions struct {
ID int64 `json:"id"`
UserID int64 `json:"-"`
Description json.RawMessage `json:"description"`
}
// now you can use gorm to query the permissions that are related to a user
user := User{}
err := db.Preload("Permissions").First(&user, userID).Error
if err != nil {
log.Fatal(err)
}
// now you can access user.Permissions[i].Description for example.
// or marshal to json
out, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
*: I am assuming that the relationship between User and UserPermission is One-to-many. This may very well not be the case, and a Many-to-many relationship actually makes sense (One user can have many permissions, one permission can belong to many users). If this is the case, the concepts are the same and you should be able to modify this solution following the Gorm guide on many to many relationships.

How to change binding of required tag for different endpoints in Golang (Gin)?

TL;DR
I'm looking for way to to get rid of the binding:"required" tag for specific endpoints (such as PUT) in Gin govalidate.
Issue
For the POST route, the password to create a new user is required.
For the PUT route, the password is not required, but still needs to be alphanumeric and a minimum of 8 characters.
Tried
Using different packages to see if I can change the binding when the endpoint is hit.
Created another struct called UserUpdate but feel this is really messy, particularly when the struct is large.
Using an embedded struct with such as below, however this couldn't be converted to the type User (I'm not sure if this is possible)
type User struct {
Password string `db:"password" json:"password,omitempty" "binding:min=8,alpha"
User
}
Example
// User Struct
type User struct {
Id int `db:"id" json:"id"`
FirstName string `db:"first_name" json:"first_name" binding:"required"`
LastName string `db:"last_name" json:"last_name" binding:"required"`
Password string `db:"password" json:"password,omitempty" "binding:required,min=8,alpha"`
}
// Update in Controller
func (c *UserController) Update(g *gin.Context) {
// Change from binding to nothing here!
var u domain.User
if err := g.ShouldBindJSON(&u); err != nil {
Respond(g, 400, "Validation failed", err)
return
}
}
I feel there must be something cleaner to tackle this problem!
Here is something similar, but doesn't quite answer the issue:
golang - elegant way to omit a json property from being serialized

Why isn't mongo populating embedded structs? [duplicate]

Hypothetical, I run an API and when a user makes a GET request on the user resource, I will return relevant fields as a JSON
type User struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
Secret string `json:"-,omitempty" bson:"secret,omitempty"`
}
As you can see, the Secret field in User has json:"-". This implies that in most operation that I would not like to return. In this case, a response would be
{
"id":1,
"Name": "John"
}
The field secret will not be returned as json:"-" omits the field.
Now, I am openning an admin only route where I would like to return the secret field. However, that would mean duplicating the User struct.
My current solution looks like this:
type adminUser struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}
Is there a way to embed User into adminUser? Kind of like inheritance:
type adminUser struct {
User
Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}
The above currently does not work, as only the field secret will be returned in this case.
Note: In the actual code base, there are few dozens fields. As such, the cost of duplicating code is high.
The actual mongo query is below:
func getUser(w http.ResponseWriter, r *http.Request) {
....omitted code...
var user adminUser
err := common.GetDB(r).C("users").Find(
bson.M{"_id": userId},
).One(&user)
if err != nil {
return
}
common.ServeJSON(w, &user)
}
You should take a look at the bson package's inline flag
(that is documented under bson.Marshal).
It should allow you to do something like this:
type adminUser struct {
User `bson:",inline"`
Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}
However, now you'll notice that you get duplicate key errors
when you try to read from the database with this structure,
since both adminUser and User contain the key secret.
In your case I would remove the Secret field from User
and only have the one in adminUser.
Then whenever you need to write to the secret field,
make sure you use an adminUser.
Another alternative would be to declare an interface.
type SecureModel interface {
SecureMe()
}
Make sure your model implements it:
type User struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Username string `json:"username" bson:"username"`
Secret string `json:"secret,omitempty" bson:"secret"`
}
func (u *User) SecureMe() {
u.Secret = ""
}
And only call it depending on which route is called.
// I am being sent to a non-admin, secure me.
if _, ok := user.(SecureModel); ok {
user.(SecureModel).SecureMe()
}
// Marshall to JSON, etc.
...
Edit: The reason for using an interface here is for cases where you might send arbitrary models over the wire using a common method.

Resources