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

Tutorial Go para Web com Redis

Go para Web usando Redis

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. 😉

 

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>