I have a function here to create post requests and add a new user of type struct into a slice (the data for the API is just running in memory, so therefore no database):
type user struct {
ID string `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
}
var users = []user{
{ID: "1", FirstName: "John", LastName: "Doe", Email: "john.doe#email.com"},
}
func createUser(c *gin.Context) {
var newUser user
if len(newUser.ID) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": "user id is null"})
return
}
users = append(users, newUser)
c.JSON(http.StatusCreated, newUser)
}
Eveything worked fine, until i tried to make an 'if statement' that checks if an id for a user being sent with a post request is null.
The problem is that the if statement returns true, when it should return false. So if i for example try to make a post request with the following data:
{
"id": "2",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe#email.com"
}
The if statement that checks for an id with the length of 0 will be true, and therefore return a StatusBadRequest. If have also tried this way:
if newUser.ID == "" {
}
But this also returns true when it shouldn't.
If i remove this check and create the POST request it works just fine, and the new added data will appear when i make a new GET request.
Why do these if statements return true?
When you create a new user object with var newUser user statement, you are just creating an empty user object. You still have to bind the JSON string you are sending into that object. for that, what you need to do is: c.BindJSON(&newUser). full code will be like:
func createUser(c *gin.Context) {
var newUser user
err := c.BindJSON(&newUser)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
if len(newUser.ID) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": "user id is null"})
return
}
users = append(users, newUser)
c.JSON(http.StatusCreated, newUser)
}
you can check this link for an example provided by Gin: https://github.com/gin-gonic/examples/blob/master/basic/main.go#L61
Related
I am just starting to get used to gqlgen to create a golang based graphql api for a personal project I am working on. This is my first attempt at adding a user node into the db, the code works (it took a while, I am new to go neo4j and graphql).
My problem is it feels very coupled to the db, my coding style would be to abstract away the db operations from this code. I don't feel sufficiently experienced to achieve this so I am looking for advice to improve before heading into further programming. I have 25+ years experience of different languages SQL, C++, PHP, Basic, Java, Javascript, Pascal, etc so happy with programming and databases (not so much graph databases).
Code from schema.resolvers.go
// UpsertUser adds or updates a user in the system
func (r *mutationResolver) UpsertUser(ctx context.Context, input model.UserInput) (*model.User, error) {
// Update or insert?
var userId string
if input.ID != nil {
userId = *input.ID
} else {
newUuid, err := uuid.NewV4() // Create a Version 4 UUID.
if err != nil {
return nil, fmt.Errorf("UUID creation error %v", err)
}
userId = newUuid.String()
}
// Open session
session := r.DbDriver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite})
defer func(session neo4j.Session) {
err := session.Close()
if err != nil {
}
}(session)
// Start write data to neo4j
neo4jWriteResult, neo4jWriteErr := session.WriteTransaction(
func(transaction neo4j.Transaction) (interface{}, error) {
transactionResult, driverNativeErr :=
transaction.Run(
"MERGE (u:User {uuid: $uuid}) ON CREATE SET u.uuid = $uuid, u.name = $name, u.userType = $userType ON MATCH SET u.uuid = $uuid, u.name = $name, u.userType = $userType RETURN u.uuid, u.name, u.userType",
map[string]interface{}{"uuid": userId, "name": input.Name, "userType": input.UserType})
// Raw driver error
if driverNativeErr != nil {
return nil, driverNativeErr
}
// If result returned
if transactionResult.Next() {
// Return the created nodes data
return &model.User{
ID: transactionResult.Record().Values[0].(string),
Name: transactionResult.Record().Values[1].(string),
UserType: model.UserType(transactionResult.Record().Values[2].(string)),
}, nil
}
// Node wasn't created there was an error return this
return nil, transactionResult.Err()
})
// End write data to neo4j
// write failed
if neo4jWriteErr != nil {
return nil, neo4jWriteErr
}
// write success
return neo4jWriteResult.(*model.User), nil
}
Code from resolver.go
type Resolver struct {
DbDriver neo4j.Driver
}
schema.graphqls
enum UserType {
"Administrator account"
ADMIN
"Tutor account"
TUTOR
"Student account"
STUDENT
"Unvalidated Account"
UNVALIDATED
"Suspended Account"
SUSPENDED
"Retired account"
RETIRED
"Scheduled for deletion"
DELETE
}
type User {
id: ID!
name: String!
userType: UserType!
}
input UserInput {
id: String
name: String!
userType: UserType!
}
type Mutation {
upsertUser(input: UserInput!) : User!
}
I am building a map[string]interface{} dynamically to create users from an imported CSV file. The map looks alright when I print it, but when I want to create it (add to my database) with Gorm I get an error.
for i := range results {
userMap := map[string]interface{}{}
for c := range choices {
if choices[c] != "Unspecified" {
userMap[choices[c]] = results[i][c]
}
}
fmt.Printf("%+v\n", userMap)
err = db.Model(&models.User{}).Create(userMap).Error
if err != nil {
fmt.Println(err)
}
}
The error I receive is:
zero-length delimited identifier at or near """"
I also activated the Gorm debugger and that outputs:
←[33m[2022-01-28 12:05:04]←[0m ←[36;1m[22.72ms]←[0m INSERT INTO ""
DEFAULT VALUES RETURNING "".* ←[36;31m[0 rows affected or returned
]←[0m
I checked the official documentation and it states that a create should be possible when using a map[string]interface{}.
I am clueless on why it is not working. Hope someone can help me out.
P.S. The outcome of the printf is:
map[CreatedAt:1/28/2022 8:25 Email:name#privder.nl FirstName:John
LastName:Doe
Password:$2a$23$nZWRc/UD9swTuoeF7XC2mdOrIEMCJzr3H1qrkaNQiE0AUcJrhWmCC]
My User model:
package models
import (
"time"
)
type User struct {
Id int `gorm:"AUTO_INCREMENT"`
CompanyId int `gorm:""`
FirstName string `gorm:"size:24"`
LastName string `gorm:"size:24"`
Email string `gorm:"size:64"`
Password string `gorm:"size:128"`
Activation Activation `gorm:"size:64"`
CreatedAt time.Time `gorm:"DEFAULT:current_timestamp"`
UpdatedAt time.Time `gorm:"DEFAULT:null"`
DeletedAt time.Time `gorm:"DEFAULT:null"`
}
If I would do this, it works perfectly fine:
err = db.Model(&models.User{}).Create(&models.User{
FirstName: "123",
LastName: "456",
Email: "789#nl.nl",
Password: "sdfsdfnsdfsjdkfh",
}).Error
if err != nil {
fmt.Println(err)
}
But when I do this, it doesnt work:
err = db.Model(&models.User{}).Create(map[string]interface{}{
"FirstName": "AAAAAA",
"LastName": "AAAAAA",
"Email": "aaa#bbbb.com",
"Password": "sdfsdfnsdfsjdkfh",
}).Error
if err != nil {
fmt.Println(err)
}
That results in the error:
INSERT INTO "" DEFAULT VALUES RETURNING "".*
I am using the latest (as far as I know) version of Gorm too. In my go.mod file it states:
github.com/jinzhu/gorm v1.9.16
I have the following related tables:
type Person struct {
ID uint64 `json:"id" gorm:"primary_key;auto_increment"`
Name string `json:"name"`
Surname string `json:"surname"`
}
type Book struct {
ID uint64 `json:"id" gorm:"primary_key;auto_increment"`
Title string `json:"title" binding:"required,min=2,max=100" gorm:"type:varchar(100)"`
Author Person `json:"author" binding:"required" gorm:"foreignkey:AuthorID"` // * here
AuthorID uint64 `json:"-"` // * here
WrittenIn string `json:"written_in" gorm:"type:varchar(5)"`
CreatedAt time.Time `json:"created_at" gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:CURRENT_TIMESTAMP"`
}
I can successfully create data with Create() method of gorm using this function:
func CreateBook(ctx *gin.Context) {
// validating input
var inputData CreateBookInput
if err := ctx.ShouldBindJSON(&inputData); err != nil {
ctx.JSON(401, gin.H{"status": "fail", "error": err.Error()})
}
// create book
book := models.Book{Title: inputData.Title, AuthorID: inputData.AuthorID, WrittenIn: inputData.WrittenIn}
database.DB.Create(&book).Preload("Author")
// database.DB.Preload("Author").Create(&book)
// database.DB.Set("gorm:auto_preload", true).Create(&book)
ctx.JSON(201, gin.H{"status": "success", "book": book})
}
I want to return the newly created book with its author. Expected response:
"book": {
"id": 10,
"title": "Chinor ostidagi duel",
"author": {
"id": 3,
"name": "John",
"surname": "Smith"
},
"written_in": "1983",
"created_at": "2022-01-07T17:07:50.84473824+05:00",
"updated_at": "2022-01-07T17:07:50.84473824+05:00"
}
But I couldn't find a way to populate related 'author'. So what I get is:
"book": {
"id": 10,
"title": "Chinor ostidagi duel",
"author": {
"id": 0, // empty
"name": "", // empty
"surname": "" // empty
},
"written_in": "1983",
"created_at": "2022-01-07T17:07:50.84473824+05:00",
"updated_at": "2022-01-07T17:07:50.84473824+05:00"
}
Itried these methods with no success:
database.DB.Create(&book).Preload("Author")
database.DB.Preload("Author").Create(&book)
database.DB.Set("gorm:auto_preload", true).Create(&book)
database.DB.Create(&book).Set("gorm:auto_preload", true)
How can I populate related field of newly created data?
One possible solution that you could try is to use the AfterCreate hook.
func (b *Book) AfterCreate(tx *gorm.DB) (err error) {
return tx.Model(b).Preload("Author").Error
}
You can find more info about hooks here.
Preload is chain method, Create is finisher method. only finisher method will generate and execute SQL.
So...
1 Find author by id after create book
if err ;= database.DB.Create(&book).Error; err != nil {
return err
}
// will not throw not found error
database.DB.Limit(1).Find(&book.Author, book.AuthorID)
ctx.JSON(201, gin.H{"status": "success", "book": book})
2 Load author data every times, use hook
// create and update
// func (b *Book) AfterSave(tx *gorm.DB) (err error) {
// just create
func (b *Book) AfterCreate(tx *gorm.DB) (err error) {
// handle error if you want
tx.Limit(1).Find(&b.Author, b.AuthorID)
return
}
PROBLEM
the mutation below
mutation {
signUp(signUpInput: {email: "newuser#gmail.com", username: "newUser", password: "asdfasdfawerawer"}) {
email
username
}
}
errors out the following
{
"errors": [
{
"message": "Cannot query field \"email\" on type \"SignUpResponse\".",
"locations": [
{
"line": 3,
"column": 5
}
]
},
{
"message": "Cannot query field \"username\" on type \"SignUpResponse\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
EXPECTATION
{
"data": {
"signUp": {
"email": "newuser#gmail.com",
"username": "newUser"
}
}
}
snippets of code
schema.graphql snippet
...
input SignUpInput {
username: String!
email: String!
password: String!
}
type Mutation {
signUp(signUpInput: SignUpInput): SignUpResponse!
}
type SignUpResponse {
ok: Boolean!
error: String
addedUser: User
}
resolvers.go snippet
...
// UserResolver ingests properties from User
type UserResolver struct{ u *User }
// UserID returns the userId of the user
func (r *UserResolver) UserID() graphql.ID {
return r.u.UserID
}
// Username returns the username of the user
func (r *UserResolver) Username() string {
return r.u.Username
}
// Email returns the email of the user
func (r *UserResolver) Email() string {
return r.u.Email
}
// Password returns the password of the user
func (r *UserResolver) Password() string {
return r.u.Password
}
type SignUpArgs struct {
Username string
Email string
Password string
}
// SignUp returns a new User from Db and its responses
func (r *RootResolver) SignUp(args struct{ SignUpInput *SignUpArgs }) (*SignUpResolver, error) {
// Find user:
u, err := r.Db.CreateUser(args.SignUpInput)
// need to deal with this different, so sort of error if we can't create the user
// a. user already exists
// b. email already exists
if err != nil {
// error creating the user
msg := "already signed up"
return &SignUpResolver{
Status: false,
Msg: &msg,
User: nil,
}, err
}
return &SignUpResolver{
Status: true,
Msg: nil,
User: &UserResolver{&u},
}, nil
}
// SignUpResolver is the response type
type SignUpResolver struct {
Status bool
Msg *string
User *UserResolver
}
// Ok for SignUpResponse
func (r *SignUpResolver) Ok() bool {
return r.Status
}
// Error for SignUpResponse
func (r *SignUpResolver) Error() *string {
return r.Msg
}
// AddedUser for SignUpResponse
func (r *SignUpResolver) AddedUser() *UserResolver {
return r.User
}
postgres.go - db operations
// User returns a single user
func (d *Db) User(uid graphql.ID) (User, error) {
var (
sqlStatement = `SELECT * FROM users WHERE user_id=$1;`
row *sql.Row
err error
u User
)
row = d.QueryRow(sqlStatement, uid)
err = row.Scan(
&u.UserID,
&u.Username,
&u.Email,
&u.Password,
)
util.Check(err, "row.Scan")
return u, nil
}
// CreateUser - inserts a new user
func (d *Db) CreateUser(i *SignUpArgs) (User, error) {
var (
sqlStatement = `
INSERT INTO users (email, username, password)
VALUES ($1, $2, $3)
RETURNING user_id`
userID graphql.ID
row *sql.Row
err error
u User
)
/***************************************************************************
* retrieve the UserID of the newly inserted record
* db.Exec() requires the Result interface with the
LastInsertId() method which relies on a returned value from postgresQL
* lib/pq does not however return the last inserted record
****************************************************************************/
row = d.QueryRow(sqlStatement, i.Email, i.Username, i.Password)
if err = row.Scan(&userID); err != nil {
// err: username or email is not unqiue --> user already exsits
return u, err
}
u, _ = d.User(userID)
return u, nil
}
I've tried to change the CreateUser to this
// CreateUser - inserts a new user
func (d *Db) CreateUser(i *SignUpArgs) (User, error) {
var (
sqlStatement = `
INSERT INTO users (email, username, password)
VALUES ($1, $2, $3)
RETURNING user_id`
userID graphql.ID
row *sql.Row
err error
u User
)
/***************************************************************************
* retrieve the UserID of the newly inserted record
* db.Exec() requires the Result interface with the
LastInsertId() method which relies on a returned value from postgresQL
* lib/pq does not however return the last inserted record
****************************************************************************/
row = d.QueryRow(sqlStatement, i.Email, i.Username, i.Password)
if err = row.Scan(&userID); err != nil {
// err: username or email is not unqiue --> user already exsits
return u, err
}
err = row.Scan(
&u.UserID,
&u.Username,
&u.Email,
&u.Password,
)
util.Check(err, "row.Scan User")
return u, nil
}
didn't do it obviously. hence the question, why the query error? seems like UserResolver can't return the User data provided the row is being returned from db.
Your type definitions include:
type Mutation {
signUp(signUpInput: SignUpInput): SignUpResponse!
}
type SignUpResponse {
ok: Boolean!
error: String
addedUser: User
}
It appears you're attempting to query the fields for the returned User, but signUp does not return a User object. Instead, signUp returns a SignUpResponse object, which, as the error states, does not have any fields named email or username.
The correct query would look something like this:
mutation {
signUp(signUpInput: {email: "newuser#gmail.com", username: "newUser", password: "asdfasdfawerawer"}) {
addedUser {
email
username
}
ok
error
}
}
I have a User struct:
type User struct {
gorm.Model
Email string
Password string
AccountType int
CompanyId int
FirstName string
LastName string
PhoneNumber string
RecoveryEmail string
Contractor bool `gorm:"sql:'not null' default:'false'"`
}
I'm using this struct to get a row from the database using gorm:
// Get a specific user from the database.
func getUser(id uint) (*User, error) {
var user User
if err := database.Connection.Select("id, created_at, email, account_type, company_id, first_name, last_name").Where("id = ? ", id).First(&user).Error; err != nil {
return nil, err
}
fmt.Println(&user)
return &user, nil
}
My Gin hanlder:
// #Summary Attempts to get a existing user by id
// #tags users
// #Router /api/users/getUserById [get]
func HandleGetUserById(c *gin.Context) {
// Were using delete params as it shares the same interface.
var json deleteParams
if err := c.Bind(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "No user ID found, please try again."})
return
}
outcome, err := getUser(json.Id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Something went wrong while trying to process that, please try again.", "error": err.Error()})
log.Println(err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Successfully found user",
"user": outcome,
})
}
It returns back everything fine, but when I return &user the fields not selected are returned back with default values:
{
"message": "Successfully found user",
"user": {
"ID": 53,
"CreatedAt": "2018-06-24T00:05:49.761736+01:00",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Email": "jack#jackner.com",
"Password": "",
"AccountType": 0,
"CompanyId": 2,
"FirstName": "",
"LastName": "",
"PhoneNumber": "",
"RecoveryEmail": "",
"Contractor": false
}
}
Is there a way in go to remove empty or null properties from an object? Or will I have to send back an object instead with the values mapped to said new object? If there's a simple way of doing the former with a helper function I'd like to know how.
You can specify the omitempty tag in your object's fields definitions.
Example:
Email string `json:",omitempty"`
If you define the fields that way, empty values will not be present in the JSON output:
https://golang.org/pkg/encoding/json/#Marshal
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.