Replace incoming post request data before bind with Go Gin? - go

I made a simple post API to store articles in database using Gorm And Go Gin.
problem with API showing when I tried to post the category name instead of the category id because the struct declaring it as int32 type
I made a simple function to getting the id of the category instead of the name but i don't know how to inject it within the incoming request
The article model
package models
import (
"gorm.io/gorm"
)
type News struct {
Id int `gorm:"primary_key;auto_increment;not_null" json:"id"`
Category int32 `gorm:"category" json:"category" binding:"required"`
CountryID int32 `gorm:"country_id" json:"country_id" binding:"required"`
LangID int32 `gorm:"lang_id" json:"lang_id" binding:"required"`
SourceId int32 `gorm:"source_id" json:"source_id" binding:"required"`
HashtagId int32 `gorm:"hashtag_id" json:"hashtag_id" binding:"required"`
Title string `gorm:"title" json:"title" binding:"required"`
Content string `gorm:"content" json:"content" binding:"required"`
Description string `gorm:"description" json:"description" binding:"required"`
Summery string `gorm:"summery" json:"summery" binding:"required"`
ImageUrl string `gorm:"image_url" json:"image_url" binding:"required"`
SourceUrl string `gorm:"source_url" json:"source_url" binding:"required"`
Author string `gorm:"author" json:"author" `
}
func CreateArticle(db *gorm.DB, article *News) (err error) {
err = db.Create(article).Error
if err != nil {
return err
}
return nil
}
Category Model
package models
import "gorm.io/gorm"
type Category struct {
Id int32 `gorm:"primary_key;auto_increment;not_null" json:"id"`
Name string `gorm:"category_name" json:"category" binding:"required"`
Parent int32 `gorm:"parent" json:"parent"`
}
func GetCategoryByName(db *gorm.DB, category *Category, name string) (err error) {
err = db.Where("LOWER(category_name) Like LOWER(?)", "%"+name+"%").First(category).Error
if err != nil {
return err
}
return nil
}
News Controller which implement the models
package controllers
import (
"errors"
"net/http"
"github.com/fouad82/news-app-go/models"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gorm.io/gorm"
)
type NewsRepo struct {
db *gorm.DB
}
func NewArticle() *NewsRepo {
db := models.InitDb()
return &NewsRepo{db: db}
}
// CreateArticle Create Article
func (repository *NewsRepo) CreateArticle(c *gin.Context) {
var Category models.Category
if err := c.ShouldBindBodyWith(&Category, binding.JSON); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": &Category})
return
}
err := models.GetCategoryByName(repository.db, &Category, Category.Name)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.AbortWithStatus(http.StatusNotFound)
return
}
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"status": "error", "error": err})
return
}
var CategoryId = Category.Id
var Article models.News
Article.Category = CategoryId
if err := c.ShouldBindBodyWith(&Article, binding.JSON); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": err})
return
}
err = models.GetArticleByTitle(repository.db, &Article, Article.Title)
if err == nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.AbortWithStatus(http.StatusNotFound)
return
}
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"status": "error", "error": "Title Created Before"})
return
}
createArticle := models.CreateArticle(repository.db, &Article)
if createArticle != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"status": "error", "error": createArticle})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok", "result": "Article Created"})
}
in this line i tried to inject the int value within the article struct abut it still getting error that im passing string value not int and this because it still read from the request json not the injected parameter
var CategoryId = Category.Id
var Article models.News
Article.Category = CategoryId

I believe the function you are using is out of request scope, so it's basically can not affect the gin.Context.
What I suggest is to copy the context in order to modify it with your changed data. Article.Category in specific.
...
c.Copy
...
The other thing is about the function - it's evaluating itself - I would suggest to exclude it from CreateArticle function
createArticle := models.CreateArticle(repository.db, &Article)
What I also suggest is to return changed struct and work with it separately from request payload - probably you need to create another function for that:
func (repository *NewsRepo) ChangedArticle(c *gin.Context)News{
changedNews := News{
Id: 0,
Category: 0, // Here is where you change it
CountryID: 0,
LangID: 0,
SourceId: 0,
HashtagId: 0,
Title: "",
Content: "",
Description: "",
Summery: "",
ImageUrl: "",
SourceUrl: "",
Author: "",
}
return changedNews
}
and than in your main function - just use it:
...
createArticle := models.ChangedArticle
...
I could miss some details, but I hope this top level explanation helps.

Related

How to return nested entities after creating a new object?

Model Account contains nested structures - Currency and User
When I create a new instance of Account in DB, and then return it in my response, nested entities are empty:
type Account struct {
BaseModel
Name string `gorm:"size:64;not null" json:"name"`
Balance decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
UserID int `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
CurrencyID int `gorm:"not null" json:"-"`
Currency Currency `gorm:"foreignKey:CurrencyID" json:"currency"`
}
type CreateAccountBody struct {
Name string `json:"name" binding:"required"`
Balance decimal.Decimal `json:"balance"`
CurrencyID int `json:"currency_id" binding:"required"`
}
func CreateAccount(ctx *gin.Context) {
body := CreateAccountBody{}
if err := ctx.Bind(&body); err != nil {
log.Println("Error while binding body:", err)
ctx.JSON(
http.StatusBadRequest,
gin.H{"error": "Wrong request parameters"},
)
return
}
account := Account {
Name: body.Name,
Balance: body.Balance,
CurrencyID: body.CurrencyID,
UserID: 1,
}
if result := db.DB.Create(&account); result.Error != nil {
log.Println("Unable to create an account:", result.Error)
}
ctx.JSON(http.StatusCreated, gin.H{"data": account})
}
To avoid this problem, I refresh account variable with separate query:
db.DB.Create(&account)
db.DB.Preload("User").Preload("Currency").Find(&account, account.ID)
ctx.JSON(http.StatusCreated, gin.H{"data": account})
Is this the most effective and correct way to achieve the desired result?
I'm gonna share you how usually I managed this scenario. First, let me share the code.
main.go file
package main
import (
"context"
"gogindemo/handlers"
"github.com/gin-gonic/gin"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
db *gorm.DB
ctx *gin.Context
)
func init() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&handlers.Currency{})
db.AutoMigrate(&handlers.User{})
db.AutoMigrate(&handlers.Account{})
}
func AddDb() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "DB", db))
ctx.Next()
}
}
func main() {
db.Create(&handlers.User{Id: 1, Name: "john doe"})
db.Create(&handlers.User{Id: 2, Name: "mary hut"})
db.Create(&handlers.Currency{Id: 1, Name: "EUR"})
db.Create(&handlers.Currency{Id: 2, Name: "USD"})
r := gin.Default()
r.POST("/account", AddDb(), handlers.CreateAccount)
r.Run()
}
Here, I've just added the code for bootstrapping the database objects and add some dummy data to it.
handlers/handlers.go file
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
type User struct {
Id int
Name string
}
type Currency struct {
Id int
Name string
}
type Account struct {
Id int
Name string `gorm:"size:64;not null" json:"name"`
Balance decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
UserID int `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
CurrencyID int `gorm:"not null" json:"-"`
Currency Currency `gorm:"foreignKey:CurrencyID" json:"currency"`
}
type CreateAccountBody struct {
Name string `json:"name" binding:"required"`
Balance decimal.Decimal `json:"balance"`
CurrencyID int `json:"currency_id" binding:"required"`
}
func CreateAccount(c *gin.Context) {
db, ok := c.Request.Context().Value("DB").(*gorm.DB)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
var accountReq CreateAccountBody
if err := c.BindJSON(&accountReq); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "wrong request body payload"})
return
}
// create Account & update the "account" variable
account := Account{Name: accountReq.Name, Balance: accountReq.Balance, CurrencyID: accountReq.CurrencyID, UserID: 1}
db.Create(&account).Preload("Currency").Preload("User").Find(&account, account.Id)
c.IndentedJSON(http.StatusCreated, account)
}
Within this file, I actually talk with the database through the DB passed in the context. Now, back to your question.
If the relationship between the Currency/Account and User/Account is of type 1:1, then, you should rely on the Preload clause. This will load the related entity in a separate query instead of adding it in an INNER JOIN clause.
Let me know if this solves your issue, thanks!

Error:interface must be a pointer to struct Error Returned in fiber (golang), how to solve this?

I'm new in Golang Programming
I'm Faceing an issue..
I'm trying to acces my sent body data by "BodyParser"functtion
But I got an error
schema: interface must be a pointer to struct
I'm Giving the Function Bellow
func CreateService(c *fiber.Ctx) error {
if c.Locals("user_type") != "1" {
return c.SendString("Wrong One")
}
file, err := c.FormFile("image")
// Check for errors:
if err != nil {
fmt.Println(err.Error())
return c.JSON("Something error")
}
// 👷 Save file to root directory:
c.SaveFile(file, fmt.Sprintf("./%s", file.Filename))
// 👷 Save file inside uploads folder under current working directory:
c.SaveFile(file, fmt.Sprintf("./uploads/%s", file.Filename))
// 👷 Save file using a relative path:
c.SaveFile(file, fmt.Sprintf("/tmp/uploads_relative/%s", file.Filename))
var data map[string]string
if err := c.BodyParser(&data); err != nil {
return err
}
service := models.Services{
Title: data["title"],
Src: PORT + "/" + file.Filename,
}
database.DB.Create(&service)
return c.JSON(service)
}
model.Services is
type Services struct {
Id uint `json:"id"`
Title string `json:"title"`
Src string `json:"src"`
}
Please Help me out. Thanks a lot in advance!!!
We need to provide pointer to a struct in GoFiber_v2 not *map[string]string. This is causing the issue.
Below Function for getting data from GoFiber_v2 BodyParser:
func Register(c *fiber.Ctx) error {
var user models.User
err := c.BodyParser(&user)
if err != nil {
return err
}
fmt.Printf("%#+v", user)
}
Model Package
package models
type User struct {
Id uint `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Password string `json:"password"`
}

Parsing Nested JSON string

I am trying to parse a nested json string
I did get it to work by using multiple structs, but I am wondering if I can parse the JSON without using an extra struct.
type Events struct {
Events []Event `json:"events"`
}
type Event struct {
Name string `json:"name"`
Url string `json:"url"`
Dates struct {
Start struct {
LocalDate string
LocalTime string
}
}
}
type Embed struct {
TM Events `json:"_embedded"`
}
func TMGetEventsByCategory(location string, category string) {
parameters := "city=" + location + "&classificationName=" + category + "&apikey=" + api_key
tmUrl := tmBaseUrl + parameters
resp, err := http.Get(tmUrl)
var embed Embed
var tm Event
if err != nil {
log.Printf("The HTTP request failed with error %s\n", err)
} else {
data, _ := ioutil.ReadAll(resp.Body)
err := json.Unmarshal(data, &embed)
json.Unmarshal(data, &tm)
}
}
JSON Data looks like this:
{
"_embedded": {
"events": [],
},
"OtherStuff": {
}
}
Is it possible to get rid of the Embed struct and read straight to the events part of the json string?
What you're looking for here is json.RawMessage. It can help delay parsing of certain values, and in you case map[string]json.RawMessage should represent the top-level object where you can selectively parse values. Here's a simplified example you can adjust to your case:
package main
import (
"encoding/json"
"fmt"
)
type Event struct {
Name string `json:"name"`
Url string `json:"url"`
}
func main() {
bb := []byte(`
{
"event": {"name": "joe", "url": "event://101"},
"otherstuff": 15.2,
"anotherstuff": 100
}`)
var m map[string]json.RawMessage
if err := json.Unmarshal(bb, &m); err != nil {
panic(err)
}
if eventRaw, ok := m["event"]; ok {
var event Event
if err := json.Unmarshal(eventRaw, &event); err != nil {
panic(err)
}
fmt.Println("Parsed Event:", event)
} else {
fmt.Println("Can't find 'event' key in JSON")
}
}
In your case look for the _embedded key and then Unmarshal its value to Events
yes of course
type Embed struct {
TM []struct {
Name string `json:"name"`
Url string `json:"url"`
Dates struct {
Start struct {
LocalDate string
LocalTime string
}
} // add tag here if you want
} `json:"_embedded"`
}

UnmarshalMap using aws-go-sdk

// UserInfo 用来解构返回的数据
type UserInfo struct {
gender string `dynamo:"gender"`
product string `dynamo:"product"`
id string `dynamo:"id"`
createTime int `dynamo:"create_time"`
name string `dynamo:"name"`
}
// GetUserInfoByID 根据userId在supe_user表取回用户信息
func GetUserInfoByID(userId string) (UserInfo, error) {
queryInput := dynamodb.GetItemInput{
Key: map[string]*dynamodb.AttributeValue{
"userId": {
S: aws.String(userId),
},
},
TableName: aws.String("user"),
}
result, err := dbsession.DynamoDB.GetItem(&queryInput)
userInfo := UserInfo{}
if err != nil {
fmt.Println(err.Error())
return userInfo, err
}
unmarshalMapErr := dynamodbattribute.UnmarshalMap(result.Item, &userInfo)
if unmarshalMapErr != nil {
return userInfo, err
}
fmt.Println(result.Item)
fmt.Println(userInfo.name)
return userInfo, nil
}
Why is this not working? It did not throw any error, just not working...
My guess is something wrong with my UserInfo type, but can't figure the right way to do this, help, please.
In Go, a name is exported if it begins with a capital letter. You should make first letters of fields UPPERCASED to make sure they're exported, like:
type UserInfo struct {
Gender string `dynamo:"gender"`
Product string `dynamo:"product"`
Id string `dynamo:"id"`
CreateTime int `dynamo:"create_time"`
Name string `dynamo:"name"`
}
more info: https://www.goinggo.net/2014/03/exportedunexported-identifiers-in-go.html

undefined (cannot refer to unexported field or method)

I'm trying to refer Users struct from the models package and trying to access the model from control.But I take following errors.
controllers/user.go:87: user.create_date undefined (cannot refer to unexported field or method create_date)
controllers/user.go:88: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:104: user.user_id undefined (cannot refer to unexported field or method user_id)
controllers/user.go:119: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:136: user.user_id undefined (cannot refer to unexported field or method user_id)
controllers/user.go:151: user.update_date undefined (cannot refer to unexported field or method update_date)
controllers/user.go:166: user.user_id undefined (cannot refer to unexported field or method user_id)
Models.go
package models
import(
"time"
)
type Users struct {
user_id int `json:"user_id" form:"user_id" gorm:"column:user_id"`
user_login string `json:"user_login" form:"user_login" gorm:"column:user_login"`
user_email string `json:"user_email" form:"user_email" gorm:"column:user_email"`
user_password string `json:"user_password" form:"user_password" gorm:"column:user_password"`
user_password_salt string `json:"user_password_salt" form:"user_password_salt" gorm:"column:user_password_salt"`
user_2factor_secret string `json:"user_2factor_secret" form:"user_2factor_secret" gorm:"column:user_2factor_secret"`
user_fullname string `json:"user_fullname" form:"user_fullname" gorm:"column:user_fullname"`
user_description string `json:"user_description" form:"user_description" gorm:"column:user_description"`
user_enabled string `json:"user_enabled" form:"user_enabled" gorm:"column:user_enabled"`
user_verified string `json:"user_verified" form:"user_verified" gorm:"column:user_verified"`
PublisherInfoID int `json:"PublisherInfoID" form:"PublisherInfoID" gorm:"column:PublisherInfoID"`
DemandCustomerInfoID int `json:"DemandCustomerInfoID" form:"DemandCustomerInfoID" gorm:"column:DemandCustomerInfoID"`
create_date time.Time `json:"create_date" gorm:"column:create_date"`
update_date time.Time `json:"update_date" gorm:"column:update_date"`
user_permission_cache string `json:"user_permission_cache" form:"user_permission_cache" gorm:"column:user_permission_cache"`
user_role int `json:"user_role" form:"user_role" gorm:"column:user_role"`
}
in controllers
package controllers
import (
"time"
"github.com/op/go-logging"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/go-sql-driver/mysql"
"../models"
)
var loguser = logging.MustGetLogger("AdsAPI")
type AdsControllerUser struct {
DB gorm.DB
}
func (ac *AdsControllerUser) SetDB(d gorm.DB) {
ac.DB = d
ac.DB.LogMode(true)
}
func (ac *AdsControllerUser) CreateUsers(c *gin.Context) {
var user models.Users
// This will infer what binder to use depending on the content-type header.
c.Bind(&user)
// Update Timestamps
user.create_date = time.Now()
user.update_date = time.Now()
err := ac.DB.Save(&user)
if err != nil {
loguser.Debugf("Error while creating a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to create user",
}
c.JSON(404, res)
return
}
content := gin.H{
"status": "201",
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
func (ac *AdsControllerUser) UpdateUsers(c *gin.Context) {
// Grab id
id := c.Params.ByName("id")
var user models.Users
c.Bind(&user)
// Update Timestamps
user.update_date = time.Now()
//err := ac.DB.Model(&models.auth_User).Where("user_id = ?", id).Updates(&cm)
err := ac.DB.Where("user_id = ?", id).Updates(&user)
if err != nil {
loguser.Debugf("Error while updating a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to update user",
}
c.JSON(403, res)
return
}
content := gin.H{
"status": "201",
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
func (ac *AdsControllerUser) DeleteUsers(c *gin.Context) {
// Grab id
id := c.Params.ByName("id")
var user models.Users
c.Bind(&user)
// Update Timestamps
user.update_date = time.Now()
err := ac.DB.Where("user_id = ?", id).Delete(&user)
if err != nil {
loguser.Debugf("Error while deleting a user, the error is '%v'", err)
res := gin.H{
"status": "403",
"error": "Unable to delete user",
}
c.JSON(403, res)
return
}
content := gin.H {
"result": "Success",
"UserID": user.user_id,
}
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, content)
}
Use Capitals for exported fields in struct, when referring struct in another package.
package models
import (
"time"
)
type Users struct {
ID int `json:"user_id" form:"user_id" gorm:"column:user_id"`
Login string `json:"user_login" form:"user_login" gorm:"column:user_login"`
Email string `json:"user_email" form:"user_email" gorm:"column:user_email"`
Password string `json:"user_password" form:"user_password" gorm:"column:user_password"`
PasswordSalt string `json:"user_password_salt" form:"user_password_salt" gorm:"column:user_password_salt"`
TwoFactorSecret string `json:"user_2factor_secret" form:"user_2factor_secret" gorm:"column:user_2factor_secret"`
Fullname string `json:"user_fullname" form:"user_fullname" gorm:"column:user_fullname"`
Description string `json:"user_description" form:"user_description" gorm:"column:user_description"`
Enabled string `json:"user_enabled" form:"user_enabled" gorm:"column:user_enabled"`
Verified string `json:"user_verified" form:"user_verified" gorm:"column:user_verified"`
PublisherInfoID int `json:"PublisherInfoID" form:"PublisherInfoID" gorm:"column:PublisherInfoID"`
DemandCustomerInfoID int `json:"DemandCustomerInfoID" form:"DemandCustomerInfoID" gorm:"column:DemandCustomerInfoID"`
CreateDate time.Time `json:"create_date" gorm:"column:create_date"`
UpdateDate time.Time `json:"update_date" gorm:"column:update_date"`
PermissionCache string `json:"user_permission_cache" form:"user_permission_cache" gorm:"column:user_permission_cache"`
Role int `json:"user_role" form:"user_role" gorm:"column:user_role"`
}
Now do Users.ID to get fields.

Resources