Beego Session Not Passing Data to Template Automatically - session

Upon getting session information of type map[string]interface{} with this.GetSession("session_key"), I did have to explicitly set the context and type assert the session like this in order to explicitly pass the data to the template.
// Get the session
profile := this.GetSession("profile")
// Have to add data to the template's context
this.Data["nickname"] = profile.(map[string]interface{})["nickname"].(string)
this.Data["picture"] = profile.(map[string]interface{})["picture"].(string)
// Render template
this.TplNames = "user.html"
The session data (type map[string]interface{}) looks like this:
{"nickname": "joe", "picture": "urltotheimg"}
However, according to the Beego's session doc, it looks like the session is passed implicitly without any need of type assertions or context passing (the template has immediate access to session values i.e. {{.nickname}} and {{.picture}})
This is the controller setting the session before redirecting to /user
// Inherit beego's base controller
type MyController struct {
beego.Controller
}
func (this *MyController) Get() {
// code for getting token here
// Getting the User information
client := conf.Client(oauth2.NoContext, token)
resp, err := client.Get("https://" + domain + "/userinfo")
if err != nil {
this.Redirect("/error", 500)
return
}
// Reading the body for user's information
raw, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
this.Redirect("/error", 500)
return
}
// Unmarshalling the JSON of the Profile
var profile map[string]interface{}
if err := json.Unmarshal(raw, &profile); err != nil {
this.Redirect("/error", 500)
return
}
// Saving the information to the session.
this.SetSession("profile", profile)
// redirect to /user
this.Redirect("/user", 301)
}
This is the controller of "/user"
type UserController struct {
beego.Controller
}
func (this *UserController) Get() {
// get the saved session
profile := this.GetSession("profile")
// without setting the template data here, the session data won't be
// available in user.html
this.Data["nickname"] = profile.(map[string]interface{})["nickname"].(string)
this.Data["picture"] = profile.(map[string]interface{})["picture"].(string)
this.TplNames = "user.html"
}
Only this then I can map the template to the data like this:
<img src="{{ .picture }}">
<p>Hello, {{ .nickname }}</p>
I'm quite sure it's necessary to set the template data. I'm just not sure why the above doc didn't do that.
Any help would be appreciated.

I just tried running the Beego quickstart project and ran it successfully.
Make sure you have both the beego and bee installed. After creating a new project with bee new projectname make sure you edit the projectname/conf/app.conf file and add the sessionon = true:
appname = quickstart
httpport = 8080
runmode = dev
sessionon = true
I created a redirect controller like:
type RedirectController struct {
beego.Controller
}
func (c *RedirectController) Get() {
profile := make(map[string]interface{})
profile["nickname"] = "User's Nickname"
profile["picture"] = "/path/to/img.jpg"
c.SetSession("profile", profile)
c.Redirect("/", 301)
}
The main controller:
type MainController struct {
beego.Controller
}
func (c *MainController) Get() {
profile := c.GetSession("profile")
c.Data["nickname"] = profile.(map[string]interface{})["nickname"]
c.Data["picture"] = profile.(map[string]interface{})["picture"]
c.TplNames = "index.tpl"
}
My index.tpl file:
<p>Nickname: {{.nickname}}</p>
<p>Picture: {{.picture}}</p>
And the router:
func init() {
beego.Router("/", &controllers.MainController{})
beego.Router("/redirect", &controllers.RedirectController{})
}
I would also recommend you to use a structure to store the profile values like:
// Define struct.
type Profile struct{
Nickname string
Picture string
}
// Save it for template rendering.
this.Data["profile"] = &Profile{Nickname:"astaxie", Picture:"img.jpg"}
// And render it like this:
Nickname: {{.profile.Nickname}}
Picture: {{.profile.Picture}}
Make sure to read this to understand how template rendering is done. I hope this is what you were asking for, if not, please edit your question and add more helpful information and I will edit this answer.

Related

Bind Query inside middleware

I'm trying to write a "Binder" middleware that will validate any request query using a struct type with gin bindings/validators
So for example, let's say I have an endpoint group called /api/subject which requires the query string to have a subject code and an ID that will be validated using the following struct (called entity.Subject):
type Subject struct {
Code string `binding:"required,alphanum"`
ID string `binding:"required,alphanum,len=4"`
}
That's just one example, but I'd like to be able to pass any struct type to this middleware, because I'd like to access the query data on future handlers without worrying about query validation.
So I tried something like this:
func Binder(t reflect.Type) gin.HandlerFunc {
return func(c *gin.Context) {
obj := reflect.New(t).Elem().Interface()
if err := c.BindQuery(&obj); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
c.Set(t.Name(), obj)
}
}
And added this middleware like so:
apiGroup := router.Group("/api")
{
// other subgroups/endpoints
// ...
subjectGroup := apiGroup.Group("/subject", middleware.Binder(reflect.TypeOf(entity.Subject{})))
}
And later on, in another handler function, let's say GetSubject, I want to access the subject data passed by doing c.MustGet("Subject").(entity.Subject)
But this isn't working =(, when I print obj, it's just an empty interface, how would I do this?
I managed to do something similar!
I created the following middleware
var allowedTypes = []binding.Binding{
binding.Query,
binding.Form,
binding.FormPost,
binding.FormMultipart,
}
func Bind(name string, data interface{}, bindingType binding.Binding) gin.HandlerFunc {
return func(ctx *gin.Context) {
ok := false
for _, b := range allowedTypes {
if b == bindingType {
ok = true
}
}
if !ok {
ctx.AbortWithError(
http.StatusInternalServerError,
fmt.Errorf("Bind function only allows %v\n", allowedTypes),
)
}
_ = ctx.MustBindWith(data, bindingType)
ctx.Set(name, data)
}
}
Remember to pass a pointer to your desired type in the call, like so:
router.GET("/something", Bind("Object", &myObject, binding.Query))
I restricted only to a few binding types because they allow ShouldBind to be called multiple times, whereas JSON, XML and others consume the Request body.
This way you can pass multiple Bind middlewares and if the validation fails it automatically aborts with http.StatusBadRequest

Cannot bind POST body to URL in Go

I'm trying to make a simple API call to the pokemon API through reaching a POST request that I'm serving with Echo.
I'm sending a POST request to "localhost:8000/pokemon" with the body { "pokemon": "pikachu" } where the BODY is reattached to the request through ioutil changing the request to be made with the body: "localhost:8000/pokemon/pikachu".
The POST request works by responding with some JSON, but the call being made is only to "localhost:8000/pokemon", and it seems the body isn't added to the URL.
I think there is something wrong with the binding here u := new(pokemon)
Anyone have any ideas?
func main() {
e := echo.New() // Middleware
e.Use(middleware.Logger()) // Logger
e.Use(middleware.Recover())
//CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
}))
// Root route => handler
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
})
e.POST("/pokemon", controllers.GrabPrice) // Price endpoint
// Server
e.Logger.Fatal(e.Start(":8000"))
}
type pokemon struct { pokemon string `json:"pokemon" form:"pokemon" query:"pokemon"`
}
// GrabPrice - handler method for binding JSON body and scraping for stock price
func GrabPrice(c echo.Context) (err error) {
// Read the Body content
var bodyBytes []byte
if c.Request().Body != nil {
bodyBytes, _ = ioutil.ReadAll(c.Request().Body)
}
// Restore the io.ReadCloser to its original state
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
u := new(pokemon)
er := c.Bind(u) // bind the structure with the context body
// on no panic!
if er != nil {
panic(er)
}
// company ticker
ticker := u.pokemon
print("Here", string(u.pokemon))
// yahoo finance base URL
baseURL := "https://pokeapi.co/api/v2/pokemon"
print(baseURL + ticker)
// price XPath
//pricePath := "//*[#name=\"static\"]"
// load HTML document by binding base url and passed in ticker
doc, err := htmlquery.LoadURL(baseURL + ticker)
// uh oh :( freak out!!
if err != nil {
panic(err)
}
// HTML Node
// from the Node get inner text
price := string(htmlquery.InnerText(doc))
return c.JSON(http.StatusOK, price)
}
Adding to what already answered by #mkopriva and #A.Lorefice
Yes you need to ensure that the variable are exported, for the binding to work properly.
Since underlay process of binding actually using reflection mechanism on the struct. See this documentation, scroll into Structs section to see what it is.
type pokemon struct {
Pokemon string `json:"pokemon" form:"pokemon" query:"pokemon"`
}

Unsure how to overwrite a callback to adjust unexported field

What I'd like to do is overwrite some boolean values on a given object, such as:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.bareSession.State.TrackRoles = false;
manager.bareSession.State.TrackPresences = false;
// more stuff happens down here
}
However bareSession is an unexported field, so I need to do this differently from what I'm gathering. I've come across some approaches using reflection but I'd like to learn the best practices approach to doing this.
In my specific case, it looks like the library I'm using offers a method to accomplish this. I've been tinkering with how to overwrite/define such a method but can't seem to figure out how to go about this.
What's the ideal approach to defining this SessionFunc() to customize the session the way I'm trying to?
I don't know the context of that library, so I'm not sure if what I'll write here makes sense for you :) But by looking at the API, SessionFunc is a func(token string) (*discordgo.Session, error), i.e., a function which receives a string and returns a Session and an error. So you can make something like this to override it:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = func(token string) (*discordgo.Session, error) {
// use "token"
// if invalid:
if token == "" {
// return an error
return nil, fmt.Errorf("invalid token")
}
// otherwise, return a valid session
return &discordgo.Session{}
}
// more stuff happens down here
}
The code is obviously very generic, but the main idea is that you need to define that function with that exact header, and implement it. I don't know how you can, for example, evaluate the token parameter or create a discordgo.Session. Or how you can configure the TrackRoles or TrackPresences values by using SessionFunc. That's very specific for that library only, but I guess it makes more sense to you than to me :)
You could also define a regular function elsewhere with that exact header:
func createNewSession(token string) (*discordgo.Session, error) {
// use "token"
// if invalid:
if token == "" {
// return an error
return nil, fmt.Errorf("invalid token")
}
// otherwise, return a valid session
return &discordgo.Session{}
}
And set it with:
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = createNewSession
// more stuff happens down here
}
Both approaches work the same way.
Reflection is [almost] never the recommended way to do those things, if the library creators made those properties private, they shouldn't be changed/accessed from outside.
This will not allow you to change bareSession. But if you need to derive a new *discordgo.Session with custom parameters you can do something similar to as follows.
func MySessionFunc(m *dshardmanager.Manager) dshardmanager.SessionFunc {
return func(token string) (*discordgo.Session, error) {
//Call default Session allocator
s, err := m.StdSessionFunc(token)
if err != nil {
return nil, err
}
//Then, change its exported fields
s.State.TrackRoles = false
s.TrackPresences = false
return s, nil
}
}
func main() {
manager := dshardmanager.New("Bot " + token)
manager.SessionFunc = MySessionFunc(manager)
}

How to use Go's type alias to make own models work with protobufs?

I've got some REST API with my models defined as Go structs.
type User struct {
FirstName string
LastName string
}
Then I've got my database methods for getting data.
GetUserByID(id int) (*User, error)
Now I'd like to replace my REST API with https://github.com/twitchtv/twirp .
Therefore I started defining my models inside .proto files.
message User {
string first_name = 2;
string last_name = 3;
}
Now I've got two User types. Let's call them the native and the proto type.
I've also got a service defined in my .proto file which returns a user to the frontend.
service Users {
rpc GetUser(Id) returns (User);
}
This generates an interface that I have to fill in.
func (s *Server) GetUser(context.Context, id) (*User, error) {
// i'd like to reuse my existing database methods
u, err := db.GetUserByID(id)
// handle error
// do more stuff
return u, nil
}
Unfortunately this does not work. My database returns a native User but the interface requires a proto user.
Is there an easy way to make it work? Maybe using type aliases?
Thanks a lot!
One way you can solve your problem is by doing the conversion manually.
type User struct {
FirstName string
LastName string
}
type protoUser struct {
firstName string
lastName string
}
func main() {
u := db() // Retrieve a user from a mocked db
fmt.Println("Before:")
fmt.Printf("%#v\n", *u) // What db returns (*protoUser)
fmt.Println("After:")
fmt.Printf("%#v\n", u.AsUser()) // What conversion returns (User)
}
// Mocked db that returns pointer to protoUser
func db() *protoUser {
pu := protoUser{"John", "Dough"}
return &pu
}
// Conversion method (converts protoUser into a User)
func (pu *protoUser) AsUser() User {
return User{pu.firstName, pu.lastName}
}
The key part is the AsUser method on the protoUser struct.
There we simply write our custom logic for converting a protoUser into a User type we want to be working with.
Working Example
As #Peter mentioned in the comment section.
I've seen a project which made it with a custom Convert function. It converts the Protobuf to local struct via json.Unmarshal, not sure how's the performance but it's a way to go.
Preview Code PLAYGROUND
// Convert converts the in struct to out struct via `json.Unmarshal`
func Convert(in interface{}, out interface{}) error {
j, err := json.Marshal(in)
if err != nil {
return err
}
err = json.Unmarshal(j, &out)
if err != nil {
return err
}
return nil
}
func main() {
// Converts the protobuf struct to local struct via json.Unmarshal
var localUser User
if err := convert(protoUser, &localUser); err != nil {
panic(err)
}
}
Output
Before:
main.ProtoUser{FirstName:"John", LastName:"Dough"}
After:
main.User{FirstName:"John", LastName:"Dough"}
Program exited.

How to add filters in beego

I have started developing web application where the back end is Go. I'm using beego framework to develop this application.Previously i used to program in java. Java has a filter function to filter the request by url. I came to know that we can implement it in beego after reading the documentation. There they have given the following example code
var FilterUser = func(ctx *context.Context) {
if strings.HasPrefix(ctx.Input.URL(), "/login") {
return
}
_, ok := ctx.Input.Session("uid").(int)
if !ok {
ctx.Redirect(302, "/login")
}
}
beego.InsertFilter("/*", beego.BeforeRouter, FilterUser)
The problem is I don't know where to use this block of code.
You can do something like the following:
Set the URL you want to protect in router and the corresponding filter
Create a filter function which will be called by the router and check the user
In more detail:
// A URL set in router.go
beego.InsertFilter("/admin/", beego.BeforeRouter, controllers.ProtectAdminPages)
// A Filter that runs before the controller
// Filter to protect admin pages
var ProtectAdminPages = func(ctx *context.Context) {
sess, _ := beego.GlobalSessions.SessionStart(ctx.ResponseWriter, ctx.Request)
defer sess.SessionRelease(ctx.ResponseWriter)
// read the session from the request
ses := sess.Get("mysession")
if ses != nil {
s := ses.(map[string]string)
// get the key identifying the user id
userId, _ := strconv.Atoi(s["id"])
// a utility function that searches the database
// gets the user and checks for admin privileges
if !utils.UserIsAdmin(userId) {
ctx.Redirect(301, "/some-other-page")
}
} else {
ctx.Redirect(301, "/")
}
}

Resources