I was learning the Go language and tested Google Cloud Functions with go + Google Firestore as the database.
While I was testing the response I got inconsistent responses.
I have used the json Marshaller to convert Firebase data to Json object to return from the API, this API is hosted in the Google Cloud Functions.
// Package p contains an HTTP Cloud Function.
package p
import (
"context"
"fmt"
"log"
"net/http"
s "strings"
"encoding/json"
"cloud.google.com/go/firestore"
"google.golang.org/api/iterator"
)
func HelloWorld(w http.ResponseWriter, r *http.Request) {
path := s.Replace(r.URL.Path, "/", "", -1)
ctx := context.Background()
client := createClient(ctx)
iter := client.Collection("profile").Where("publicUrl", "==", path).Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
var publicDTO PublicDTO
var Profile Profile
doc.DataTo(&Profile)
publicDTO.Profile = Profile
b, err := json.Marshal(publicDTO)
if err != nil {
fmt.Println(err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
}
func createClient(ctx context.Context) *firestore.Client {
projectID := "projectId"
client, err := firestore.NewClient(ctx, projectID)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
return client
}
type PublicDTO struct {
Profile Profile `json:"profile"`
}
type Profile struct {
Id string `json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
FullName string `json:"fullName"`
Email string `json:"email"`
ImageUrl string `json:"imageUrl"`
CoverPic string `json:"coverPic"`
Experience int `json:"experience"`
PhoneNumber string `json:"phoneNumber"`
Description string `json:"description"`
Address string `json:"address"`
State string `json:"state"`
Country string `json:"country"`
Dob map[string]string `json:"dob"`
Website string `json:"website"`
Reputation int `json:"reputation"`
MemberFrom map[string]int `json:"memberFrom"`
Title string `json:"title"`
Organization string `json:"organization"`
Status string `json:"status"`
Setup int `json:"setup"`
Social map[string]string `json:"social"`
PublicUrl string `json:"publicUrl"`
Language []string `json:"language"`
Interests []string `json:"interests"`
}
but each time the response i'm getting is inconsistent, some of the values are missing.
The solution i got after marshal and unmarshal, it works as expected.
package p
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
s "strings"
"time"
"cloud.google.com/go/firestore"
"google.golang.org/api/iterator"
)
var ctx context.Context
var client *firestore.Client
func PublicApi(w http.ResponseWriter, r *http.Request) {
path := s.Replace(r.URL.Path, "/", "", -1)
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "*")
newFsConfigBytes, _ := json.Marshal(getPublicDTO(path))
w.Write(newFsConfigBytes)
}
func getPublicDTO(path string) (publicDTO publicDTO) {
ctx = context.Background()
client = createClient()
profile, id := getProfiles(path)
publicDTO.Profile = profile
publicDTO.MilestoneDTOS = getMilestone(id)
return
}
func getProfiles(link string) (profile, string) {
var retVal profile
var id string
iter := client.Collection("profile").Where("publicUrl", "==", link).Documents(ctx)
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
id = doc.Ref.ID
b, _ := json.Marshal(doc.Data())
json.Unmarshal(b, &retVal)
}
return retVal, id
}
func getMilestone(id string) []milestoneDTOS {
var retVal []milestoneDTOS
iter := client.Collection("milestone").Where("userId", "==", id).Documents(ctx)
for {
var milestoneDTO milestoneDTOS
doc, err := iter.Next()
if err == iterator.Done {
break
}
b, _ := json.Marshal(doc.Data())
err = json.Unmarshal(b, &milestoneDTO)
if err != nil {
fmt.Println(err)
}
retVal = append(retVal, milestoneDTO)
}
return retVal
}
func createClient() *firestore.Client {
projectID := "app_id_asda"
client, err := firestore.NewClient(ctx, projectID)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
return client
}
type profile struct {
Address string `json:"address"`
City string `json:"city"`
Country string `json:"country"`
CoverPic string `json:"coverPic"`
CreatedBy string `json:"createdBy"`
CreatedDate int `json:"createdDate"`
Description string `json:"description"`
Dob int64 `json:"dob"`
Email string `json:"email"`
Enabled bool `json:"enabled"`
Experience int `json:"experience"`
FirstName string `json:"firstName"`
FullName string `json:"fullName"`
FullNameNoSpace string `json:"fullNameNoSpace"`
ImageURL string `json:"imageUrl"`
Interests []string `json:"interests"`
IsEnabled bool `json:"isEnabled"`
Language string `json:"language"`
LastModifiedDate int `json:"lastModifiedDate"`
LastName string `json:"lastName"`
LatLng string `json:"latLng"`
MemberFrom time.Time `json:"memberFrom"`
ObjectID string `json:"objectID"`
Organization string `json:"organization"`
PhoneNumber string `json:"phoneNumber"`
PlanID string `json:"planId"`
PublicURL string `json:"publicUrl"`
Reputation int `json:"reputation"`
Setup int `json:"setup"`
Social string `json:"social"`
State string `json:"state"`
Status string `json:"status"`
Title string `json:"title"`
Website string `json:"website"`
}
type milestoneDTOS struct {
Category string `json:"category"`
CreatedBy string `json:"createdBy"`
CreatedDate int `json:"createdDate"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
EndDate time.Time `json:"endDate"`
IsCurrentPosition bool `json:"isCurrentPosition"`
IsEnabled bool `json:"isEnabled"`
LastModifiedBy time.Time `json:"lastModifiedBy"`
LastModifiedDate int `json:"lastModifiedDate"`
ObjectID string `json:"objectID"`
Organization string `json:"organization"`
PictureURL string `json:"pictureURL"`
Profile string `json:"profile"`
Score float64 `json:"score"`
StartDate time.Time `json:"startDate"`
Tags []string `json:"tags"`
Title string `json:"title"`
URL string `json:"url"`
UserID string `json:"userId"`
}
type publicDTO struct {
Profile profile `json:"profile"`
MilestoneDTOS []milestoneDTOS `json:"milestoneDTOS"`
}
Related
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!
So I want to update movie_genre data with different genre_id values, each movie has more than 1 genre_id automatically there are multiple records in movie_genre with the same movie_id and different genre_id,
when I start trying to update with gorm by selecting only 1 genre_id, the old genre has changed, but the newly changed genre is created again in a new row with all the same column values except created_at, whereas if I try to update by selecting 2 genres, the data the changed only has the value of the first genre_id, the second genre_id is not read
this is a erd database
payload json from frontend
{
"title":"Autaaa sint nihil quis ",
"release_date":"2001-01-01",
"runtime":"22",
"mpaa_rating":"G",
"rating":"4",
"description":"Eius enim distinctio1",
"id":"82",
"genre_id":["3","8"]
}:
handler
func (app *Application) UpdateMovie(ctx *gin.Context) {
var payload web.MoviePayloadResponse
if err := ctx.BindJSON(&payload); err != nil {
log.Println(err)
return
}
id, _ := strconv.Atoi(payload.ID)
movie, err := app.models.Repository.GetMovieById(id)
if err != nil {
helper.NotFound(*ctx, err)
return
}
movie.ID, _ = strconv.Atoi(payload.ID)
movie.Title = payload.Title
movie.Description = payload.Description
movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate)
movie.Year = movie.ReleaseDate.Year()
movie.Runtime, _ = strconv.Atoi(payload.Runtime)
movie.Rating, _ = strconv.Atoi(payload.Rating)
movie.MPAARating = payload.MPAARating
movie.UpdatedAt = time.Now()
err = app.models.Repository.UpdateMovie(movie, id)
if err != nil {
panic(err)
}
movieGenre, _ := app.models.Repository.GetMovieGenresById(id)
for _, mg := range *movieGenre {
mg.MovieID = movie.ID
mg.UpdatedAt = time.Now()
for _, v := range payload.GenreID {
mg.GenreID, _ = strconv.Atoi(v)
err := app.models.Repository.UpdateMovieGenre(&mg, id)
if err != nil {
panic(err)
}
}
}
ctx.JSON(http.StatusOK, &gin.H{
"ok": "response",
})
return
}
repository
func (MovieRepositoryImpl *MovieRepositoryImpl) UpdateMovie(movie *Movie, id int) error {
err := MovieRepositoryImpl.DB.Where("id = ?", id).Save(&movie).Error
if err != nil {
return err
}
return nil
}
func (MovieRepositoryImpl *MovieRepositoryImpl) UpdateMovieGenre(movieGenre *MovieGenre, id int) error {
err := MovieRepositoryImpl.DB.Where("movie_id = ?", id).Save(&movieGenre).Error
if err != nil {
return err
}
return nil
}
func (MovieRepositoryImpl *MovieRepositoryImpl) GetMovieGenresById(id int) (*[]MovieGenre, error) {
var movie []MovieGenre
err := MovieRepositoryImpl.DB.Where("movie_id = ?", id).Find(&movie).Error
if err != nil {
return nil, err
}
return &movie, nil
}
Model
type Movie struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year int `json:"year"`
ReleaseDate time.Time `json:"release_date"`
Runtime int `json:"runtime"`
Rating int `json:"rating"`
MPAARating string `json:"mpaa_rating"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Genres []Genre `json:"genres" gorm:"many2many:movie_genres"`
MovieGenre []MovieGenre `json:"movie_genres" gorm:"many2many:movie_genres"`
}
type Genre struct {
ID int `json:"id"`
GenreName string `json:"name"`
Movies []Movie `json:"movies" gorm:"many2many:movie_genres"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
type MovieGenre struct {
ID int `json:"id"`
MovieID int `json:"movie_id"`
Movie Movie `gorm:"foreignKey:MovieID"`
GenreID int `json:"genre_id"`
Genre Genre `gorm:"foreignKey:GenreID"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
type MoviePayloadResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year string `json:"year"`
ReleaseDate string `json:"release_date"`
Runtime string `json:"runtime"`
Rating string `json:"rating"`
MPAARating string `json:"mpaa_rating"`
GenreID []string `json:"genre_id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
My expectation is that the data genre_id that changes is only data whose value is not the same as the value in the payload, if it is the same then the data does not change, if it is different then it changes, and if the data in the payload exceeds the original genre data, then new data is automatically created. That's roughly what it looks like. Please help!!
I have a table that represents my user data. There is a field that represents a phone number, and I want to store the hash of that automatically in the database at either update or insert.
so my model is :
type Users struct {
gorm.Model
ID uint `gorm:"autoIncrement;unique" json:"id"`
PhoneNumber string `json:"phone_number"`
HashID string `gorm:"primaryKey" json:"hash_id"`
Name string `gorm:"default:dear user" json:"name"`
Rank uint `json:"rank"`
Score uint `json:"score"`
Image string `json:"image"`
Email string `json:"email"`
Address string `json:"address"`
Birthday string `json:"birthday"`
Biography string `json:"biography"
}
How can I tell the GORM to fill the HashID column with the sha256 hash code of the PhoneNumber column while inserting or updating data?
You need something like this:
package main
import (
"crypto/sha256"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Users struct {
gorm.Model
Key string `json:"phone_number"`
Hash string `gorm:"primaryKey" json:"hash_id"`
}
func (u *Users) BeforeCreate(tx *gorm.DB) (err error) {
h := sha256.Sum256([]byte(u.Key))
u.Hash = fmt.Sprintf("%x", h[:])
return nil
}
func (u *Users) BeforeSave(tx *gorm.DB) (err error) {
h := sha256.Sum256([]byte(u.Key))
u.Hash = fmt.Sprintf("%x", h[:])
return nil
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Users{})
u := Users{Key: "123"}
db.Create(&u)
}
Check https://gorm.io/docs/index.html
I am retrieving form data using postman, but the code is too long. Is there is any method for getting the data in short form? Here's the code I am using:
Customer struct:
type Customer struct {
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `json:"email" bson:"email"`
}
type Customers []Customer
type new_user struct {
first_name string
last_name string
email string
}
Function for retrieving the form data called by the route:
function GetData(c *gin.Context){
first_name := c.PostForm("first_name")
last_name := c.PostForm("last_name")
email := c.PostForm("email")
reqBody := new(new_user)
err := c.Bind(reqBody)
if err != nil {
fmt.Println(err)
}
customer.FirstName = first_name
customer.LastName = last_name
customer.Email = email
}
I'm getting 3 form values. Suppose I need to get 50 values, then the function will be much bigger.
You can parse HTTP request body yourself, like to following
option 1:
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/json"
"log"
)
type Customer struct {
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `json:"email" bson:"email"`
}
func process(context *gin.Context) {
var customer = &Customer{}
req := context.Request
err := json.NewDecoder(req.Body).Decode(customer)
if err != nil {
log.Fatal()
}
}
option 2:
Encoding to map to decode to struct (not recommended)
import (
"github.com/gin-gonic/gin"
"encoding/json"
"bytes"
"log"
)
type Customer struct {
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `json:"email" bson:"email"`
}
func Process(context *gin.Context) {
req := context.Request
var aMap = map[string]interface{}{}
for key, values := range req.PostForm {
aMap[key]=values[0]
}
var buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(aMap)
if err != nil {
log.Fatal(err)
}
var customer = &Customer{}
json.NewDecoder(buf).Decode(customer)
if err != nil {
log.Fatal(err)
}
}
As the mkopriva tells me a short way to do this. I got the answer for it. It can be shorter by doing the following code.
type Customer struct {
FirstName string `form:"first_name" json:"first_name" bson:"first_name"`
LastName string `form:"last_name" json:"last_name" bson:"last_name"`
Email string `form:"email" json:"email" bson:"email"`
}
In the function the code is:-
customer := new(Customer)
if err := c.Bind(customer); err != nil {
return nil, err
}
fmt.Println(customer)
It will print the data from the form-data of the postman.
I have this code. What I need is to get the transaction details from the transaction ID returned from blockchain
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
type Transaction struct {
Bid string `json:"bid"`
Fun string `json:"fun"`
ID string `json:"id"`
Timestamp string `json:"timestamp"`
TraderA string `json:"traderA"`
TraderB string `json:"traderB"`
Seller string `json:"seller"`
PointAmount string `json:"pointAmount"`
PrevTransactionID string `json:"prevTransactionId"`
}
type AllTxs struct {
TXs []Transaction `json:"tx"`
}
type Transact struct {
Cert string `json:"cert"`
ChaincodeID string `json:"chaincodeID"`
Nonce string `json:"nonce"`
Payload string `json:"payload"`
Signature string `json:"signature"`
Timestamp string `json:"nanos"`
Txid string `json:"txid"`
Type int `json:"type"`
}
func main() {
resp, err := http.Get("http://blockchain_transactions_url/trans_id")
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
byteArray := []byte(body)
var t Transact
json.Unmarshal(byteArray, &t)
//I get all the values base64 encoded
st, err := base64.StdEncoding.DecodeString(t.Payload)
if err != nil {
log.Fatal(err)
}
trd := string(st)
sp := strings.Split(trd, "\n")
result := strings.Join(sp, ",")
res := strings.Replace(result, ",", `", "`, -1)
ret := strings.Replace(res, `", "`, `{"`, 1) + `"}`
byteA := []byte(ret)
var tf AllTxs
json.Unmarshal(byteA, &tf)
//the tf...
ref := Transaction{}
fmt.Println(ref.Id)
}
the t.Payload I get is
"CsYBCAESgwESgAE3ZjFhY2Y2MTgxMGRhODMyMTA5NjZiNGYzNjc2NWU5NmIxY2Q0OTliODkyNmY0MDU0YWQ5NzhlNzhkZjczMDRhOGZlMDM1ZjZhYTBhODE2YzdmNjFlNGZkZDQ1MjM4M2Q5ZmU5ZDQxNmIyZGI4YTE1YmRkMjAzZmU2N2I5OTYyZho8ChBpbml0X3RyYW5zYWN0aW9uCgYwMDAxMTcKA2dpbwoEbW9oYQoBNQoCMTIKBTk4NzczCgcyMDE3NDIy"
the tf I get is
{"??7f1acf61810da83210966b4f36765e96b1cd499b8926f4054ad978e78df7304a8fe035f6aa0a816c7f61e4fdd452383d9fe9d416b2db8a15bdd203fe67b9962f<", "init_transaction", "000117", "gio", "moha", "5", "12", "98773", "2017422"}
At the last how can I get the JSON of Transaction/AllTxs type?
Given the comment, it looks like you are trying to unmarshal the string as JSON, but as it stands, the string is not valid JSON; if it were, you would be able to unmarshal it as follows, ignoring the fact that the fields to not appear to match up with the desired structure;
package main
import (
"encoding/json"
"fmt"
)
type Transaction struct {
Bin string `json:"bin"`
Fun string `json:"fun"`
ID string `json:"id"`
Timestamp string `json:"timestamp"`
TraderA string `json:"traderA"`
TraderB string `json:"traderB"`
Seller string `json:"seller"`
PointAmount string `json:"pointAmount"`
PrevTransactionID string `json:"prevTransactionId"`
}
func main() {
snippet := `{
"bin": "7f1acf61810da83210966b4f36765e96b1cd499b8926f4054ad978e78df7304a8fe035f6aa0a816c7f61e4fdd452383d9fe9d416b2db8a15bdd203fe67b9962f",
"fun": "init_transaction",
"id": "000117",
"timestamp": "gio",
"traderA": "moha",
"traderB": "5",
"seller": "12",
"pointAmount": "98773",
"prevTransactionId": "2017422"
}`
t := Transaction{}
json.Unmarshal([]byte(snippet), &t)
fmt.Println(t)
}