Google App Engine users

Another service we are going to make use of is the Google App Engine Users API, which provides the authentication of Google accounts (and Google Apps accounts).

Create a new file called users.go and add the following code:

type User struct { 
  Key *datastore.Key `json:"id" datastore:"-"` 
  UserID string `json:"-"` 
  DisplayName string `json:"display_name"` 
  AvatarURL string `json:"avatar_url"` 
  Score int `json:"score"` 
} 

Similar to the Question struct, we have Key and a few fields that make up the User entity. This struct represents an object that belongs to our application that describes a user; we will have one for every authenticated user in our system, but this isn't the same user object that we'll get from the Users API.

Importing the https://godoc.org/google.golang.org/appengine/user package and calling the user.Current(context.Context) function will return either nil (if no user is authenticated) or a user.User object. This object belongs to the Users API and isn't suitable for our data store, so we need to write a helper function that will translate the App Engine user into our User.

Tip

Watch out that goimports doesn't automatically import os/user instead; sometimes it's best if you handle imports manually.

Add the following code to users.go:

func UserFromAEUser(ctx context.Context) (*User, error) { 
  aeuser := user.Current(ctx) 
  if aeuser == nil { 
    return nil, errors.New("not logged in") 
  } 
  var appUser User 
  appUser.Key = datastore.NewKey(ctx, "User", aeuser.ID, 0, nil) 
  err := datastore.Get(ctx, appUser.Key, &appUser) 
  if err != nil && err != datastore.ErrNoSuchEntity { 
    return nil, err 
  } 
  if err == nil { 
    return &appUser, nil 
  } 
  appUser.UserID = aeuser.ID 
  appUser.DisplayName = aeuser.String() 
  appUser.AvatarURL = gravatarURL(aeuser.Email) 
  log.Infof(ctx, "saving new user: %s", aeuser.String()) 
  appUser.Key, err = datastore.Put(ctx, appUser.Key, &appUser) 
  if err != nil { 
    return nil, err 
  } 
  return &appUser, nil 
} 

We get the currently authenticated user by calling user.Current, and if it is nil, we return with an error. This means that the user is not logged in and the operation cannot complete. Our web package will be checking and ensuring that users are logged in for us, so by the time they hit an API endpoint, we'll expect them to be authenticated.

We then create a new appUser variable (which is of our User type) and set datastore.Key. This time, we aren't making an incomplete key; instead, we are using datastore.NewKey and specifying a string ID, matching the User API ID. This key predictability means that not only will there only be one User entity per authenticated user in our application, but it also allows us to load a User entity without having to use a query.

Tip

If we had the App Engine User ID as a field instead, we would need to do a query to find the record we are interested in. Querying is a more expensive operation compared to a direct Get method, so this approach is always preferred if you can do it.

We then call datastore.Get to attempt to load the User entity. If this is the first time the user has logged in, there will be no entity and the returned error will be the special datastore.ErrNoSuchEntity variable. If that's the case, we set the appropriate fields and use datastore.Put to save it. Otherwise, we just return the loaded User.

Note

Note that we are checking for early returns in this function. This is to ensure that it is easy to read the execution flow of our code without having to follow it in and out of indented blocks. I call this the line of sight of code and have written about it on my blog at https://medium.com/@matryer.

For now, we'll use Gravatar again for avatar pictures, so add the following helper function to the bottom of users.go:

func gravatarURL(email string) string { 
  m := md5.New() 
  io.WriteString(m, strings.ToLower(email)) 
  return fmt.Sprintf("//www.gravatar.com/avatar/%x", m.Sum(nil)) 
} 

Embedding denormalized data

If you recall, our Question type doesn't take the author as User; rather, the type was UserCard. When we embed denormalized data into other entities, sometimes we will want them to look slightly different from the master entity. In our case, since we do not store the key in the User entity (remember the Key fields have datastore:"-"), we need to have a new type that stores the key.

At the bottom of users.go, add the UserCard struct and the associated helper method for User:

type UserCard struct { 
  Key         *datastore.Key `json:"id"` 
  DisplayName string         `json:"display_name"` 
  AvatarURL   string         `json:"avatar_url"` 
} 
func (u User) Card() UserCard { 
  return UserCard{ 
    Key:         u.Key, 
    DisplayName: u.DisplayName, 
    AvatarURL:   u.AvatarURL, 
  } 
} 

Note that UserCard doesn't specify a datastore tag, so the Key field will indeed be persisted in the data store. Our Card() helper function just builds and returns UserCard by copying the values of each field. This seems wasteful but offers great control, especially if you want embedded data to look very different from its original entity.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.47.208