Aula 13 - Golang para Web - Modelo Update

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
}

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

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. ;)