Aula 13 – Golang para Web – Modelo Update
Aula 13 – Golang para Web – Modelo Update
Voltar para página principal do blog
Todas as aulas desse curso
Aula 12 Aula 14
Se gostarem do conteúdo dêem um joinha 👍 na página do Código Fluente no
Facebook
Link do código fluente no Pinterest
Meus links de afiliados:
Hostinger
Digital Ocean
One.com
Código da aula: Github
Melhore seu NETWORKING
Participe de comunidades de desenvolvedores:
Fiquem a vontade para me adicionar ao linkedin.
E também para me seguir no GITHUB.
Ah, se puder, clica na estrela nos meus repositórios pra dá uma força ao meu perfil no GITHUB.
Aula 13 – Golang para Web – Modelo Update
Vamos deixar a sessão de comentários mais próximo de como as redes sociais em geral funcionam, com atualizações (social media updates – feeds), tipo um mural (wall).
Começaremos limpando e fazendo algumas configurações para deixar tudo mais fácil.
A partir disso, a primeira coisa que vamos fazer é mudar a forma como lidamos com as sessões.
Em vez de usar o nome de usuário como a chave de sessão principal, onde eles devem estar logados, vamos usar o ID que é a chave primária.
Então, vamos mudar isso no middleware/middleware.go e também no routes/routes.go, dentro do loginPostHandler(), que é onde definimos a sessão pela primeira vez.
No models/user.go vamos modificar a authenticateUser(), para receber também um ponteiro para o user, e modificar os retornos.
Vamos implementar o método GetUserId() que vai ser bem parecido com o GetUserName().
O nome do arquivo models/coment.go, vai passar a se chamar models/updates.go, vamos mudar os nomes dos método para GetUpdate() e PostUpdate(), mudar o nome do atributo de comment para body, e trocar também os nomes no templates/index.html.
Ainda nesse arquivo, que agora se chama models/updates.go, vamos definir sua estrutura e seu construtor.
Atualizar novamente o routes/routes.go, em função dessa mudança no nome do arquivo.
Os arquivos onde iremos mexer estão em azul:
│ readme.md │ └───web_app │ main.go │ ├───middleware │ middleware.go │ ├───models │ comment.go │ db.go │ user.go │ ├───routes │ routes.go │ ├───sessions │ sessions.go │ ├───static │ index.css │ ├───templates │ index.html │ login.html │ register.html │ └───utils templates.go
web_app/middleware/middleware.go
package middleware
import (
"net/http"
"../sessions"
)
func AuthRequired(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := sessions.Store.Get(r, "session")
_, ok := session.Values["user_id"]
if !ok {
http.Redirect(w, r, "/login", 302)
return
}
handler.ServeHTTP(w, r)
}
}
web_app/routes/routes.go
package routes
import (
"net/http"
"github.com/gorilla/mux"
"../middleware"
"../models"
"../sessions"
"../utils"
)
func NewRouter() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/", middleware.AuthRequired(indexGetHandler)).Methods("GET")
r.HandleFunc("/", middleware.AuthRequired(indexPostHandler)).Methods("POST")
r.HandleFunc("/login", loginGetHandler).Methods("GET")
r.HandleFunc("/login", loginPostHandler).Methods("POST")
r.HandleFunc("/register", registerGetHandler).Methods("GET")
r.HandleFunc("/register", registerPostHandler).Methods("POST")
fs := http.FileServer(http.Dir("./static/"))
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs))
return r
}
func indexGetHandler(w http.ResponseWriter, r *http.Request) {
updates, err := models.GetUpdates()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
utils.ExecuteTemplate(w, "index.html", updates)
}
func indexPostHandler(w http.ResponseWriter, r *http.Request) {
session, _ := sessions.Store.Get(r, "session")
untypedUserId := session.Values["user_id"]
userId, ok := untypedUserId.(int64)
if !ok {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
r.ParseForm()
body := r.PostForm.Get("update")
err := models.PostUpdate(userId, body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
http.Redirect(w, r, "/", 302)
}
func loginGetHandler(w http.ResponseWriter, r *http.Request) {
utils.ExecuteTemplate(w, "login.html", nil)
}
func loginPostHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
user, err := models.AuthenticateUser(username, password)
if err != nil {
switch err {
case models.ErrUserNotFound:
utils.ExecuteTemplate(w, "login.html", "unknown user")
case models.ErrInvalidLogin:
utils.ExecuteTemplate(w, "login.html", "invalid login")
default:
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
}
return
}
userId, err := user.GetId()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
session, _ := sessions.Store.Get(r, "session")
session.Values["user_id"] = userId
session.Save(r, w)
http.Redirect(w, r, "/", 302)
}
func registerGetHandler(w http.ResponseWriter, r *http.Request) {
utils.ExecuteTemplate(w, "register.html", nil)
}
func registerPostHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
err := models.RegisterUser(username, password)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal server error"))
return
}
http.Redirect(w, r, "/login", 302)
}
web_app/models/user.go
package models
import (
"fmt"
"errors"
"github.com/go-redis/redis"
"golang.org/x/crypto/bcrypt"
)
var (
ErrUserNotFound = errors.New("user not found")
ErrInvalidLogin = errors.New("invalid login")
)
type User struct {
key string
}
func NewUser(username string, hash []byte) (*User, error) {
id, err := client.Incr("user:next-id").Result()
if err != nil {
return nil, err
}
key := fmt.Sprintf("user:%d", id)
pipe := client.Pipeline()
pipe.HSet(key, "id", id)
pipe.HSet(key, "username", username)
pipe.HSet(key, "hash", hash)
pipe.HSet("user:by-username", username, id)
_, err = pipe.Exec()
if err != nil {
return nil, err
}
return &User{key}, nil
}
func (user *User) GetId() (int64, error) {
return client.HGet(user.key, "id").Int64()
}
func (user *User) GetUsername() (string, error) {
return client.HGet(user.key, "username").Result()
}
func (user *User) GetHash() ([]byte, error) {
return client.HGet(user.key, "hash").Bytes()
}
func (user *User) Authenticate(password string) error {
hash, err := user.GetHash()
if err != nil {
return err
}
err = bcrypt.CompareHashAndPassword(hash, []byte(password))
if err == bcrypt.ErrMismatchedHashAndPassword {
return ErrInvalidLogin
}
return err
}
func GetUserById(id int64) (*User, error) {
key := fmt.Sprintf("user:%d", id)
return &User{key}, nil
}
func GetUserByUsername(username string) (*User, error) {
id, err := client.HGet("user:by-username", username).Int64()
if err == redis.Nil {
return nil, ErrUserNotFound
} else if err != nil {
return nil, err
}
return GetUserById(id)
}
func AuthenticateUser(username, password string) (*User, error) {
user, err := GetUserByUsername(username)
if err != nil {
return nil, err
}
return user, user.Authenticate(password)
}
func RegisterUser(username, password string) error {
cost := bcrypt.DefaultCost
hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
if err != nil {
return err
}
_, err = NewUser(username, hash)
return err
}
web_app/models/update.go (antigo comment.go)
package models
import (
"fmt"
)
type Update struct {
key string
}
func NewUpdate(userId int64, body string) (*Update, error) {
id, err := client.Incr("update:next-id").Result()
if err != nil {
return nil, err
}
key := fmt.Sprintf("update:%d", id)
pipe := client.Pipeline()
pipe.HSet(key, "id", id)
pipe.HSet(key, "user_id", userId)
pipe.HSet(key, "body", body)
pipe.LPush("updates", id)
_, err = pipe.Exec()
if err != nil {
return nil, err
}
return &Update{key}, nil
}
func (update *Update) GetBody() (string, error) {
return client.HGet(update.key, "body").Result()
}
func (update *Update) GetUser() (*User, error) {
userId, err := client.HGet(update.key, "user_id").Int64()
if err != nil {
return nil, err
}
return GetUserById(userId)
}
func GetUpdates() ([]*Update, error) {
updateIds, err := client.LRange("updates", 0, 10).Result()
if err != nil {
return nil, err
}
updates := make([]*Update, len(updateIds))
for i, id := range updateIds {
key := "update:" + id
updates[i] = &Update{key}
}
return updates, nil
}
func PostUpdate(userId int64, body string) error {
_, err := NewUpdate(userId, body)
return err
}
Toda a parte do pipe do client redis é explicado na aula 12.
web_app/templates/index.html
<html>
<head>
<title>Updates</title>
<link rel="stylesheet" type="text/css" href="/static/index.css">
</head>
<body>
<h1>Updates</h1>
<form method="POST">
<textarea name="update"></textarea>
<div>
<button type="submit">Post Update</button>
</div>
</form>
{{ range . }}
<div>
<div>
<strong>{{ .GetUser.GetUsername }} wrote:</strong>
</div>
<div>{{ .GetBody }}</div>
</div>
{{ end }}
</body>
</html>
Link para o código dessa aula:
Github
Com todas as mudanças feitas, você pode testar para ver se tudo continua funcionando.
Ligue o redis-server:
redis-server
Ligue o servidor:
go run main.go
Acesse:
localhost:8000
Por agora é só, nos vemos próxima. 😉
Código da aula: Github
Voltar para página principal do blog
Todas as aulas desse curso
Aula 12 Aula 14
Se gostarem do conteúdo dêem um joinha 👍 na página do Código Fluente no
Facebook
Link do código fluente no Pinterest
Novamente deixo meus link de afiliados:
Hostinger
Digital Ocean
One.com
Obrigado, até a próxima e bons estudos. 😉