JSON RPC Client Go - json

I have a python server serving response through JSON-RPC. Here is a sample response from the server.
'{"jsonrpc": "2.0", "result": "Peer 1: local 10.10.0.2 remote 10.10.0.1 state CONNECT\\nPeer 2: local 10.10.0.18 remote 10.10.0.17 state ESTABLISHED\\nPeer 3: local 10.10.0.10 remote 10.10.0.9 state ESTABLISHED", "id": "839732f9-cf36-46ff-8b9b-6120250d9ce5"}'
Here is the request I need to send to the server:
'{"method":"echo","jsonrpc":"2.0","params":["test_params"],"id":"839732f9-cf36-46ff-8b9b-6120250d9ce5"}'
Here is my client with go language:
package main
import (
"fmt"
"log"
"net"
"net/rpc/jsonrpc"
)
type Args struct {
jsonrpc, id string
}
func main() {
conn, err := net.Dial("tcp", "11.21.22.221:8080")
if err != nil {
panic(err)
}
defer conn.Close()
args := Args{"2.0", "d87198f0-af92-49f8-9a7d-ab8bed5c4d17"}
var reply string
c := jsonrpc.NewClient(conn)
err = c.Call("echo", args, &reply)
if err != nil {
log.Fatal("error:", err)
}
fmt.Printf("Response: %d", reply)
}
But, when I run this client, it is not sending anything in the params. Instead it is sending empty params like this:
'{"method":"echo","params":[{}],"id":0}\n
Can somebody help me telling what mistake I am making? I am a newbie to go language.
Thanks.

I don't think what you are doing is possible using the client as provided by go because the private clientRequest struct is currently defined as:
type clientRequest struct {
Method string `json:"method"`
Params [1]interface{} `json:"params"`
Id uint64 `json:"id"`
}
What you pass into Call as args is stuck into Params and note how there is no "Version `json:"jsonrpc"`" inside of that struct.
AFAICT (which may be wrong, this is my first time reading through this code) you would need to implement your own ClientCodec. You could probably get away with copying most (all) of the parts out of the stdlib and add the field to the clientRequest above. ;-)

Related

Go Fiber not able to parse body in unit test

I am officially crying uncle to the benevolent samaritans of Stack Overflow.
I am trying to unit test my GORM (Postgres) + Fiber API using a mock DB. I have a Card model and a CreateCardReqBody model for the POST request body. To setup the test, I create a random CreateCardReqBody instance, marshal it into JSON, then pass it into an *httptest.Request. The handler uses Fiber's (*fiber.Ctx).BodyParser function to "unmarshal" the request body into an empty Card struct. However, when I run the test that is supposed to pass, Fiber throws an "Unprocessable Entity" error.
Below are the relevant parts of my code; the test file is a combination of this tutorial and Fiber's documentation on the (*App).Test method. (I realize the code could be cleaned up; I'm just trying to get a proof of life then focus on revising :)
I've done a few things to debug this: I've made a Postman POST request with the same values as the test and it works. Within the test itself, I marshal then unmarshal the CreateCardReqBody struct and that works. I've triple checked the spelling of the JSON fields match, that the struct fields are exported, etc. I've also run the VSCode debugger and the body field within Fiber.Ctx's also looks correct to me.
I'm starting to wonder if it's something with how Fiber parses the body from a test request vs. a real request. I would greatly appreciate any insight one could share on this!
Model Definition
type Card struct {
gorm.Model
// Implicit Gorm foreign key to fleet ID
FleetID uint `gorm:"index" json:"fleet_id" validate:"required,min=1"`
// Card provider's account number
ProviderAccountNumber string `json:"provider_account_number"`
// Card provider's external card identifier
CardIdentifier string `gorm:"index" json:"card_identifier" validate:"min=1"`
// Implicit Gorm foreign key to driver ID. Driver association is optional.
DriverID uint `json:"associated_driver_id" validate:"min=1"`
// Implicit Gorm foreign key to vehicle ID.
VehicleID uint `json:"associated_vehicle_id" validate:"required,min=1"`
// User-inputted start date, formatted "2020-01-26T22:38:25.000Z" in UTC
StartDate pq.NullTime
}
Test file
// Adapted from tutorial
type testCase struct {
name string
body CreateCardReqBody
setupAuth func(t *testing.T, request *http.Request)
buildStubs func(db *mockDB.MockDBInterface)
checkResponse func(response *http.Response, outputErr error)
}
type CreateCardReqBody struct {
FleetID int `json:"fleet_id"`
ProviderAccountNumber string `json:"provider_account_number"`
CardIdentifier string `json:"card_identifier"`
StartDate string `json:"start_date"`
AssociatedDriverID int `json:"associated_driver_id"`
AssociatedVehicleID int `json:"associated_vehicle_id"`
}
func TestCreateCard(t *testing.T) {
user := randomUser(t)
vehicle := randomVehicle()
driver := randomDriver(vehicle.FleetID)
okReqCard := randomCard(vehicle.FleetID)
finalOutputCard := okReqCard
finalOutputCard.ID = 1
testCases := []testCase{
{
name: "Ok",
body: CreateCardReqBody{
FleetID: int(okReqCard.FleetID),
ProviderAccountNumber: okReqCard.ProviderAccountNumber,
CardIdentifier: okReqCard.CardIdentifier,
StartDate: okReqCard.StartDate.Time.Format("2006-01-02T15:04:05.999Z"),
AssociatedDriverID: int(okReqCard.DriverID),
AssociatedVehicleID: int(okReqCard.VehicleID),
},
setupAuth: func(t *testing.T, request *http.Request) {
addAuthorization(t, request, user)
},
// Tell mock database what calls to expect and what values to return
buildStubs: func(db *mockDB.MockDBInterface) {
db.EXPECT().
UserExist(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
Times(1).Return(user, true, user.ID)
db.EXPECT().
SearchTSP(gomock.Eq(fmt.Sprint(vehicle.FleetID))).
Times(1)
db.EXPECT().
SearchVehicle(gomock.Eq(fmt.Sprint(okReqCard.VehicleID))).
Times(1).
Return(vehicle, nil)
db.EXPECT().
SearchDriver(gomock.Eq(fmt.Sprint(driver.ID))).
Times(1).
Return(driver, nil)
db.EXPECT().
CardCreate(gomock.Eq(okReqCard)).
Times(1).
Return(finalOutputCard, nil)
},
checkResponse: func(res *http.Response, outputErr error) {
require.NoError(t, outputErr)
// Internal helper func, excluded for brevity
requireBodyMatchCard(t, finalOutputCard, res.Body)
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockDB := mockDB.NewMockDBInterface(ctrl)
test.buildStubs(mockDB)
jsonBytes, err := json.Marshal(test.body)
require.NoError(t, err)
jsonBody := bytes.NewReader(jsonBytes)
// Debug check: am I able to unmarshal it back? YES.
errUnmarsh := json.Unmarshal(jsonBytes, &CreateCardReqBody{})
require.NoError(t, errUnmarsh)
endpoint := "/v1/transactions/card"
request := httptest.NewRequest("POST", endpoint, jsonBody)
// setupAuth is helper function (not shown in this post) that adds authorization to httptest request
test.setupAuth(t, request)
app := Init("test", mockDB)
res, err := app.Test(request)
test.checkResponse(res, err)
})
}
}
Route handler being tested
func (server *Server) CreateCard(c *fiber.Ctx) error {
var card models.Card
var err error
// 1) Parse POST data
if err = c.BodyParser(&card); err != nil {
return c.Status(http.StatusUnprocessableEntity).SendString(err.Error())
}
...
}
Debugger Output
Json body when defined in test
Body inside Fiber context
facepalm
I forgot to request.Header.Set("Content-Type", "application/json")! Posting this in case it's helpful for anyone else :)

JSON decoding error in Go: "invalid character '\\' looking for beginning of object key string"

I'm using the vpp package of MicroMDM but noticed that initializing a VPP client, which was working before, has stopped working. The error I get occurs in the client's GetClientContext() method (https://github.com/micromdm/micromdm/blob/ea5c0a3865e87f5de04a58dc2c67b5fa6f6fd7de/vpp/clientconfigsrv.go#L99-L117), which I've modified with a fmt.Println() call to print the context:
// GetClientContext gets the values that determine which mdm server is associated with a VPP account token
func (c *Client) GetClientContext() (*ClientContext, error) {
// Get the ClientConfigSrv info
clientConfigSrv, err := c.GetClientConfigSrv()
if err != nil {
return nil, errors.Wrap(err, "get ClientContext request")
}
// Get the ClientContext string
var context = clientConfigSrv.ClientContext
fmt.Println(context)
// Convert the string to a ClientContext type
var clientContext ClientContext
err = json.NewDecoder(strings.NewReader(context)).Decode(&clientContext)
if err != nil {
return nil, errors.Wrap(err, "decode ClientContext")
}
return &clientContext, nil
}
where ClientContext is the following struct (https://github.com/micromdm/micromdm/blob/ea5c0a3865e87f5de04a58dc2c67b5fa6f6fd7de/vpp/clientconfigsrv.go#L10-L14):
// Contains information that associates your particular mdm server to a VPP account token
type ClientContext struct {
HostName string `json:"hostname"`
GUID string `json:"guid"`
}
when I run a script that invokes vpp.NewClient() (which, in turn, invokes this method), I see the following output:
> go run main.go getVPPAssets
INFO[0000] Using config file: .vpp-client.env
{\"hostname\":\"https://your.server.com\",\"guid\":\"7c66e635-ef07-4e26-86a3-c953e4e8f16d\"}
FATA[0000] Failed to initialize VPP client: GetClientContext request: decode ClientContext: invalid character '\\' looking for beginning of object key string
exit status 1
Note that the line with
{\"hostname\":\"https://your.server.com\",\"guid\":\"7c66e635-ef07-4e26-86a3-c953e4e8f16d\"}
is produced by the fmt.Println() statement I added. I've tried to reproduce this error with the following minimal example:
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/sirupsen/logrus"
)
type ClientContext struct {
HostName string `json:"hostname"`
GUID string `json:"guid"`
}
func main() {
context := "{\"hostname\":\"https://your.server.com\",\"guid\":\"7c66e635-ef07-4e26-86a3-c953e4e8f16d\"}"
var clientContext ClientContext
if err := json.NewDecoder(strings.NewReader(context)).Decode(&clientContext); err != nil {
logrus.Fatalf("decode ClientContext: %v", err)
}
fmt.Printf("%+v\n", clientContext)
}
However, this example decodes the clientConfigSrv.ClientContext string into a ClientContext correctly.
I believe there is something about the escaping of quotes that has changed in the response returned by Apple's VPP service and is causing this decoder to choke; any ideas how to fix this error?

Replying with a JSON on an open TCP socket

I have a server that successfully opens a connection with a second server. The second server performs an action and I am trying to get it to reply to the first server with a JSON on the same connection.
package main
import (
"fmt"
"net"
"encoding/json"
)
type NewContainerJSON struct {
Action string `json:"Action"`
ContainerName string `json:"ContainerName"`
BaseServer string `json:"BaseServer"`
CMS string `json:"CMS"`
WebsiteName string `json:"WebsiteName"`
DBrootPWD string `json:"DBrootPWD"`
DBadminUname string `json:"DBadminUname"`
DBadminPWD string `json:"DBadminPWD"`
}
func main() {
service := "127.0.0.1:8081"
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
conn, err := listener.Accept()
checkError(err)
decoder := json.NewDecoder(conn)
encoder := json.NewEncoder(conn)
var b NewContainerJSON
err = decoder.Decode(&b)
checkError(err)
fmt.Println(b.Action)
if b.Action == "createNew" {
fmt.Println("This works")
resp := []byte("And here's our repomse")
conn.Write(resp)
c := NewContainerJSON {
Action: "createdNewContainer",
ContainerName: "Test",
BaseServer: "Test",
CMS: "Test",
WebsiteName: "Test",
DBrootPWD: "Test",
DBadminUname: "Test",
DBadminPWD: "Test",
}
encoder := json.NewEncoder(conn)
if err := encoder.Encode(c); err != nil {
fmt.Println("encode.Encode error: ", err)
}
conn.Write(c)
}
}
func checkError(err error) {
if err != nil {
fmt.Println("An error occurred: ", err.Error())
}
}
I get following error on the line conn.Write(c)
cannot use c (type NewContainerJSON) as type []byte in argument to conn.Write
Two questions:
1: What exactly is this error saying? It seems to be complaining that 'c' cannot be used as a Byte when using the conn.Write function but shouldn't the json.Encoder convert the JSON to a format the conn.Write can use?
2: How exactly can I return a JSON back to the first server using the open connection?
The encoder writes the JSON encoding of c to conn on this line:
if err := encoder.Encode(c); err != nil {
That's all you need to do. Delete the call to conn.Write(c).
The error message is telling you that the value of c cannot be used as the argument to Write because of a type mismatch. A NewContainerJSON is not a []byte.
You first write a string to the connection by
resp := []byte("And here's our repomse")
conn.Write(resp)
This will make it error-prone on the client side. You'll need to read exactly the same amount of data before employ the json decoder on this connection.
If a connection is used for json communication, all the messages on this stream should be json.
So if you want send a message to notify, encode that message too:
encoder.Encode(string(resp))

Golang slices of struct or newbie trouble building REST

and need your help.
Wanted to build simple api and stuck with some problem.
I've choose gin and database/sql with postgres driver
package main
import (
"database/sql"
"fmt"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
func main() {
router := gin.Default()
router.GET("/search/:text", SearchWord)
router.Run(":8080")
}
I need to make query to DB and make json out of this request.
func checkErr(err error) {
if err != nil {
panic(err)
}
}
type Message struct {
ticket_id int `json:"ticket_id"`
event string `json:"event"`
}
func SearchWord(c *gin.Context) {
word := c.Params.ByName("text")
db, err := sql.Open("postgres", "host=8.8.8.8 user= password= dbname=sample")
defer db.Close()
checkErr(err)
rows, err2 := db.Query("SELECT ticket_id,event FROM ....$1, word)
checkErr(err)
for rows.Next() {
var ticket_id int
var event string
err = rows.Scan(&ticket_id, &event)
checkErr(err)
fmt.Printf("%d | %s \n\n", ticket_id, event)
}
}
This coda working nice, but when i need to make json.
I need to make struct of a row
type Message struct {
ticket_id int `json:"ticket_id"`
event string `json:"event"`
}
an then i need to create slice , and append every rows.Next() loop an than answer to browser with Json...
c.JSON(200, messages)
But how to do that...don't know :(
disclaimer: I am brand new to go
Since you Scanned your column data into your variables, you should be able to initialize a structure with their values:
m := &Message{ticket_id: ticket_id, event: event}
You could initialize a slice with
s := make([]*Message, 0)
And then append each of your message structs after instantiation:
s = append(s, m)
Because I'm not too familiar with go there are a couple things i'm not sure about:
after copying data from query to your vars using rows.Scan does initializing the Message struct copy the current iterations values as expected??
If there is a way to get the total number of rows from your query it might be slighlty more performant to initialize a static length array, instead of a slice?
I think #inf deleted answer about marshalling your Message to json down the line might need to be addressed, and Message field's might need to be capitalized
copied from #inf:
The names of the members of your struct need be capitalized so that
they get exported and can be accessed.
type Message struct {
Ticket_id int `json:"ticket_id"`
Event string `json:"event"` }
I'm going to cheat a little here and fix a few things along the way:
First: open your database connection pool once at program start-up (and not on every request).
Second: we'll use sqlx to make it easier to marshal our database rows into our struct.
package main
var db *sqlx.DB
func main() {
var err error
// sqlx.Connect also checks that the connection works.
// sql.Open only "establishes" a pool, but doesn't ping the DB.
db, err = sqlx.Connect("postgres", "postgres:///...")
if err != nil {
log.Fatal(err)
}
router := gin.Default()
router.GET("/search/:text", SearchWord)
router.Run(":8080")
}
// in_another_file.go
type Message struct {
TicketID int `json:"ticket_id" db:"ticket_id"`
Event string `json:"event" db:"event"`
}
func SearchWord(c *gin.Context) {
word := c.Params.ByName("text")
// We create a slice of structs to marshal our rows into
var messages []*Message{}
// Our DB connection pool is safe to use concurrently from here
err := db.Select(&messages, "SELECT ticket_id,event FROM ....$1, word)
if err != nil {
http.Error(c.Writer, err.Error(), 500)
return
}
// Write it out using gin-gonic's JSON writer.
c.JSON(200, messages)
}
I hope that's clear. sqlx also takes care of calling rows.Close() for you, which will otherwise leave connections hanging.

How to access Gorm in Revel Controller?

let me start by saying these are my first couple days of toying around in Go.
I'm trying to use the Revel framework with Gorm like this:
app/controllers/gorm.go
package controllers
import (
"fmt"
"go-testapp/app/models"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"github.com/revel/revel"
)
var DB gorm.DB
func InitDB() {
var err error
DB, err = gorm.Open("mysql", "root:#/go-testapp?charset=utf8&parseTime=True")
if err != nil {
panic(err)
}
DB.LogMode(true)
DB.AutoMigrate(models.User{})
}
type GormController struct {
*revel.Controller
DB *gorm.DB
}
app/controller/app.go
package controllers
import (
"fmt"
"go-bingo/app/models"
_ "github.com/go-sql-driver/mysql"
"github.com/revel/revel"
)
type App struct {
GormController
}
func (c App) Index() revel.Result {
user := models.User{Name: "Jinzhu", Age: 18}
fmt.Println(c.DB)
c.DB.NewRecord(user)
c.DB.Create(&user)
return c.RenderJson(user)
}
After running it results in:
runtime error: invalid memory address or nil pointer dereference on line 19 c.DB.NewRecord(user)
It successfully creates the datatables with automigrate, but I have no idea how I should use Gorm in my controller.
Any hints in the right direction?
Important note
it's just replacement for GORP of original example of the Revel. And it comes with some pitfalls of the origin. This answer can be used as drop-in replacement for the original one. But it doesn't solve the pitfalls.
Please, take a look comments of this unswer and #MaxGabriel's answer that solves the pitfalls.
I'd recommend to use #MaxGabriel's solution to protect you application against some kinds of slow-* DDoS attacks. And to reduce (in some cases) DB pressure.
Original answer
#rauyran rights, you have to invoke InitDB inside init function (into controllers package).
Full example here (too much):
Tree
/app
/controllers
app.go
gorm.go
init.go
/models
user.go
[...]
user.go
// models/user.go
package models
import "time" // if you need/want
type User struct { // example user fields
Id int64
Name string
EncryptedPassword []byte
Password string `sql:"-"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time // for soft delete
}
gorm.go
//controllers/gorm.go
package controllers
import (
"github.com/jinzhu/gorm"
_ "github.com/lib/pq" // my example for postgres
// short name for revel
r "github.com/revel/revel"
// YOUR APP NAME
"yourappname/app/models"
"database/sql"
)
// type: revel controller with `*gorm.DB`
// c.Txn will keep `Gdb *gorm.DB`
type GormController struct {
*r.Controller
Txn *gorm.DB
}
// it can be used for jobs
var Gdb *gorm.DB
// init db
func InitDB() {
var err error
// open db
Gdb, err = gorm.Open("postgres", "user=uname dbname=udbname sslmode=disable password=supersecret")
if err != nil {
r.ERROR.Println("FATAL", err)
panic( err )
}
Gdb.AutoMigrate(&models.User{})
// unique index if need
//Gdb.Model(&models.User{}).AddUniqueIndex("idx_user_name", "name")
}
// transactions
// This method fills the c.Txn before each transaction
func (c *GormController) Begin() r.Result {
txn := Gdb.Begin()
if txn.Error != nil {
panic(txn.Error)
}
c.Txn = txn
return nil
}
// This method clears the c.Txn after each transaction
func (c *GormController) Commit() r.Result {
if c.Txn == nil {
return nil
}
c.Txn.Commit()
if err := c.Txn.Error; err != nil && err != sql.ErrTxDone {
panic(err)
}
c.Txn = nil
return nil
}
// This method clears the c.Txn after each transaction, too
func (c *GormController) Rollback() r.Result {
if c.Txn == nil {
return nil
}
c.Txn.Rollback()
if err := c.Txn.Error; err != nil && err != sql.ErrTxDone {
panic(err)
}
c.Txn = nil
return nil
}
app.go
package controllers
import(
"github.com/revel/revel"
"yourappname/app/models"
)
type App struct {
GormController
}
func (c App) Index() revel.Result {
user := models.User{Name: "Jinzhup"}
c.Txn.NewRecord(user)
c.Txn.Create(&user)
return c.RenderJSON(user)
}
init.go
package controllers
import "github.com/revel/revel"
func init() {
revel.OnAppStart(InitDB) // invoke InitDB function before
revel.InterceptMethod((*GormController).Begin, revel.BEFORE)
revel.InterceptMethod((*GormController).Commit, revel.AFTER)
revel.InterceptMethod((*GormController).Rollback, revel.FINALLY)
}
As you can see, it's like Revel's Booking modified for GORM.
Works fine for me. Result:
{
"Id": 5,
"Name": "Jinzhup",
"EncryptedPassword": null,
"Password": "",
"CreatedAt": "2014-09-22T17:55:14.828661062+04:00",
"UpdatedAt": "2014-09-22T17:55:14.828661062+04:00",
"DeletedAt": "0001-01-01T00:00:00Z"
}
This answer is derived from #IvanBlack's answer, at his suggestion. His version is a direct translation of the Revel example code, but we identified some problems with the original code that this answer fixes. The main changes it makes are:
The entire HTTP request is no longer wrapped by a database transaction. Wrapping the entire HTTP request keeps open the transaction far longer than necessary, which could cause a number of problems:
Your transactions will hold locks on the database, and many transactions holding locks could lead to deadlocks
More database resources are used
These problems are magnified if your HTTP requests aren't mostly limited by your SQL database access. E.g. if your HTTP request blocks for 30 seconds on making an external HTTP request or accessing another database, the slowdown on those services could affect your SQL database.
Because the transaction is no longer automatically being checked for errors at the end of the HTTP request, errors are checked as soon as the database insert is made. This is more correct as well: Imagine that after inserting the User struct into a database, you then stored the User.Id in another database like Redis. This would be fine if the database insert worked, but if it failed and you didn't immediately check the error, you would insert the default int64 value of 0 into Redis (before later rolling back only the SQL transaction).
Tree
/app
/controllers
app.go
gorm.go
init.go
/models
user.go
[...]
user.go
// models/user.go
package models
import "time" // if you need/want
type User struct { // example user fields
Id int64
Name string
EncryptedPassword []byte
Password string `sql:"-"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time // for soft delete
}
gorm.go
//controllers/gorm.go
package controllers
import (
"github.com/jinzhu/gorm"
_ "github.com/lib/pq" // my example for postgres
// short name for revel
r "github.com/revel/revel"
)
// type: revel controller with `*gorm.DB`
type GormController struct {
*r.Controller
DB *gorm.DB
}
// it can be used for jobs
var Gdb *gorm.DB
// init db
func InitDB() {
var err error
// open db
Gdb, err = gorm.Open("postgres", "user=USERNAME dbname=DBNAME sslmode=disable")
Gdb.LogMode(true) // Print SQL statements
if err != nil {
r.ERROR.Println("FATAL", err)
panic(err)
}
}
func (c *GormController) SetDB() r.Result {
c.DB = Gdb
return nil
}
init.go
package controllers
import "github.com/revel/revel"
func init() {
revel.OnAppStart(InitDB) // invoke InitDB function before
revel.InterceptMethod((*GormController).SetDB, revel.BEFORE)
}
app.go
package controllers
import(
"github.com/revel/revel"
"yourappname/app/models"
)
type App struct {
GormController
}
func (c App) Index() revel.Result {
user := models.User{Name: "Jinzhup"} // Note: In practice you should initialize all struct fields
if err := c.DB.Create(&user).Error; err != nil {
panic(err)
}
return c.RenderJSON(user)
}
Result:
{
"Id": 5,
"Name": "Jinzhup",
"EncryptedPassword": null,
"Password": "",
"CreatedAt": "2014-09-22T17:55:14.828661062+04:00",
"UpdatedAt": "2014-09-22T17:55:14.828661062+04:00",
"DeletedAt": "0001-01-01T00:00:00Z"
}
Your error is caused because you haven't initialised your c.DB database variable, it's still nil.
In your controllers/init.go file make sure you are calling revel.OnAppStart(InitDB). It should look something like this:
package controllers
import "github.com/revel/revel"
func init() {
revel.OnAppStart(InitDB)
// maybe some other init things
}
OR you need to pass in a pointer to AutoMigrate?
DB.AutoMigrate(&models.User{})