Release v0.20: регистрация, авторизация, личный кабинет
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"shop/internal/auth"
|
||||
)
|
||||
|
||||
type AccountHandler struct {
|
||||
pages *Pages
|
||||
auth *auth.Service
|
||||
}
|
||||
|
||||
func NewAccountHandler(pages *Pages, authSvc *auth.Service) *AccountHandler {
|
||||
return &AccountHandler{pages: pages, auth: authSvc}
|
||||
}
|
||||
|
||||
type accountPageData struct {
|
||||
Layout
|
||||
Name string
|
||||
}
|
||||
|
||||
func (h *AccountHandler) Account(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := h.auth.UserFromRequest(r.Context(), r)
|
||||
if err != nil || user == nil {
|
||||
http.Redirect(w, r, "/login?next=/account", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
data := accountPageData{
|
||||
Layout: h.pages.layout(r, "Личный кабинет", "account"),
|
||||
Name: user.Name,
|
||||
}
|
||||
data.Success = flashMsg(r, "ok")
|
||||
h.pages.render(w, "account.html", data)
|
||||
case http.MethodPost:
|
||||
h.updateProfile(w, r)
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AccountHandler) updateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
user, _ := h.auth.UserFromRequest(r.Context(), r)
|
||||
if user == nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Redirect(w, r, "/account", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
name := r.FormValue("name")
|
||||
if err := h.auth.UpdateName(r.Context(), user.ID, name); err != nil {
|
||||
data := accountPageData{
|
||||
Layout: h.pages.layout(r, "Личный кабинет", "account"),
|
||||
Name: name,
|
||||
}
|
||||
data.Error = err.Error()
|
||||
h.pages.render(w, "account.html", data)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/account?ok=profile", http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"shop/internal/auth"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
pages *Pages
|
||||
auth *auth.Service
|
||||
}
|
||||
|
||||
func NewAuthHandler(pages *Pages, authSvc *auth.Service) *AuthHandler {
|
||||
return &AuthHandler{pages: pages, auth: authSvc}
|
||||
}
|
||||
|
||||
type authPageData struct {
|
||||
Layout
|
||||
Email string
|
||||
Name string
|
||||
Next string
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.showRegister(w, r, "", "")
|
||||
case http.MethodPost:
|
||||
h.postRegister(w, r)
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) showRegister(w http.ResponseWriter, r *http.Request, errMsg string, email string) {
|
||||
data := authPageData{
|
||||
Layout: h.pages.layout(r, "Регистрация", "register"),
|
||||
Email: email,
|
||||
}
|
||||
data.Error = errMsg
|
||||
if msg := flashMsg(r, "ok"); msg != "" {
|
||||
data.Success = msg
|
||||
}
|
||||
h.pages.render(w, "register.html", data)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) postRegister(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
h.showRegister(w, r, "Неверные данные формы", "")
|
||||
return
|
||||
}
|
||||
email := r.FormValue("email")
|
||||
name := r.FormValue("name")
|
||||
password := r.FormValue("password")
|
||||
password2 := r.FormValue("password_confirm")
|
||||
if password != password2 {
|
||||
h.showRegister(w, r, "Пароли не совпадают", email)
|
||||
return
|
||||
}
|
||||
_, err := h.auth.Register(r.Context(), email, password, name)
|
||||
if err != nil {
|
||||
h.showRegister(w, r, err.Error(), email)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/login?ok=registered", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.showLogin(w, r, "", "")
|
||||
case http.MethodPost:
|
||||
h.postLogin(w, r)
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) showLogin(w http.ResponseWriter, r *http.Request, errMsg, email string) {
|
||||
data := authPageData{
|
||||
Layout: h.pages.layout(r, "Вход", "login"),
|
||||
Email: email,
|
||||
Next: safeNext(r.URL.Query().Get("next")),
|
||||
}
|
||||
data.Error = errMsg
|
||||
data.Success = flashMsg(r, "ok")
|
||||
if data.Layout.User != nil {
|
||||
http.Redirect(w, r, "/account", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
h.pages.render(w, "login.html", data)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) postLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
h.showLogin(w, r, "Неверные данные формы", "")
|
||||
return
|
||||
}
|
||||
email := r.FormValue("email")
|
||||
password := r.FormValue("password")
|
||||
if err := h.auth.Login(r.Context(), w, email, password); err != nil {
|
||||
msg := err.Error()
|
||||
if errors.Is(err, auth.ErrInvalidCredentials) {
|
||||
msg = "Неверный email или пароль"
|
||||
}
|
||||
h.showLogin(w, r, msg, email)
|
||||
return
|
||||
}
|
||||
next := safeNext(r.FormValue("next"))
|
||||
http.Redirect(w, r, next+"?ok=login", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
h.auth.Logout(r.Context(), w, r)
|
||||
http.Redirect(w, r, "/?ok=logout", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func safeNext(next string) string {
|
||||
next = strings.TrimSpace(next)
|
||||
if next == "" || !strings.HasPrefix(next, "/") || strings.HasPrefix(next, "//") {
|
||||
return "/account"
|
||||
}
|
||||
return next
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
@@ -10,16 +9,16 @@ import (
|
||||
)
|
||||
|
||||
type HomeHandler struct {
|
||||
products *repository.ProductRepository
|
||||
templates *template.Template
|
||||
products *repository.ProductRepository
|
||||
pages *Pages
|
||||
}
|
||||
|
||||
func NewHomeHandler(products *repository.ProductRepository, templates *template.Template) *HomeHandler {
|
||||
return &HomeHandler{products: products, templates: templates}
|
||||
func NewHomeHandler(products *repository.ProductRepository, pages *Pages) *HomeHandler {
|
||||
return &HomeHandler{products: products, pages: pages}
|
||||
}
|
||||
|
||||
type homePageData struct {
|
||||
Title string
|
||||
Layout
|
||||
Products []models.Product
|
||||
Categories []string
|
||||
TotalItems int
|
||||
@@ -52,14 +51,12 @@ func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := homePageData{
|
||||
Title: "Главная",
|
||||
Layout: h.pages.layout(r, "Главная", "home"),
|
||||
Products: featured,
|
||||
Categories: categories,
|
||||
TotalItems: total,
|
||||
}
|
||||
data.Success = flashMsg(r, "ok")
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := h.templates.ExecuteTemplate(w, "home.html", data); err != nil {
|
||||
log.Printf("render home: %v", err)
|
||||
}
|
||||
h.pages.render(w, "home.html", data)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"shop/internal/auth"
|
||||
"shop/internal/models"
|
||||
)
|
||||
|
||||
type Layout struct {
|
||||
Title string
|
||||
Nav string
|
||||
User *models.User
|
||||
Error string
|
||||
Success string
|
||||
}
|
||||
|
||||
type Pages struct {
|
||||
tmpl *template.Template
|
||||
auth *auth.Service
|
||||
}
|
||||
|
||||
func NewPages(tmpl *template.Template, authSvc *auth.Service) *Pages {
|
||||
return &Pages{tmpl: tmpl, auth: authSvc}
|
||||
}
|
||||
|
||||
func (p *Pages) layout(r *http.Request, title, nav string) Layout {
|
||||
user, _ := p.auth.UserFromRequest(r.Context(), r)
|
||||
return Layout{Title: title, Nav: nav, User: user}
|
||||
}
|
||||
|
||||
func (p *Pages) render(w http.ResponseWriter, name string, data any) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := p.tmpl.ExecuteTemplate(w, name, data); err != nil {
|
||||
http.Error(w, "ошибка шаблона", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func flashMsg(r *http.Request, key string) string {
|
||||
switch r.URL.Query().Get(key) {
|
||||
case "registered":
|
||||
return "Регистрация успешна. Войдите в аккаунт."
|
||||
case "login":
|
||||
return "Вы успешно вошли."
|
||||
case "logout":
|
||||
return "Вы вышли из аккаунта."
|
||||
case "profile":
|
||||
return "Профиль обновлён."
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user