add profile update
parent
d40367a25d
commit
b689c91d1b
|
|
@ -0,0 +1,42 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type ApiHandler interface {
|
||||
Subscribe(chi.Router) error
|
||||
}
|
||||
|
||||
type APIResp struct {
|
||||
Status int `json:"-"`
|
||||
Success bool `json:"success"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
func (a APIResp) Write(res http.ResponseWriter) {
|
||||
if a.Status != 0 {
|
||||
res.WriteHeader(a.Status)
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json; utf-8")
|
||||
enc := json.NewEncoder(res)
|
||||
enc.SetIndent("\n", "\t")
|
||||
enc.Encode(a)
|
||||
}
|
||||
|
||||
func ParseCall(model interface{}, res http.ResponseWriter, req *http.Request) error {
|
||||
defer req.Body.Close()
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&model); err != nil {
|
||||
a := APIResp{Success: false, Payload: err.Error()}
|
||||
a.Write(res)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
|
@ -9,14 +8,12 @@ import (
|
|||
|
||||
"git.vdhsn.com/adam/bodytrack/internal/services"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
)
|
||||
|
||||
type ApiHandler interface {
|
||||
Subscribe(chi.Router) error
|
||||
}
|
||||
|
||||
type UserHandler struct {
|
||||
Conn services.DB
|
||||
Conn services.DB
|
||||
JWTSessionDuration time.Duration
|
||||
}
|
||||
|
||||
func (u UserHandler) Subscribe(c chi.Router) error {
|
||||
|
|
@ -24,17 +21,90 @@ func (u UserHandler) Subscribe(c chi.Router) error {
|
|||
// c.PUT("/user", u.register)
|
||||
|
||||
c.Group(func(r chi.Router) {
|
||||
|
||||
r.Use(services.JWTVerifier)
|
||||
r.Use(jwtauth.Authenticator)
|
||||
|
||||
// r.Get("/user", u.getProfile)
|
||||
// r.Post("/user", u.updateProfile)
|
||||
|
||||
r.Get("/user", u.getProfile)
|
||||
r.Post("/user", u.updateProfile)
|
||||
// r.Post("/user/verify", u.verifyEmail)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u UserHandler) updateProfile(res http.ResponseWriter, req *http.Request) {
|
||||
fail := APIResp{Payload: "Unauthorized", Status: http.StatusUnauthorized}
|
||||
userId, err := services.GetUserIDFromClaims(req)
|
||||
if err != nil {
|
||||
fail.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
type setProfileInput struct {
|
||||
services.User
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
var in setProfileInput
|
||||
|
||||
if err := ParseCall(&in, res, req); err != nil {
|
||||
fail.Payload = "Could not read arguments"
|
||||
fail.Status = http.StatusUnprocessableEntity
|
||||
fail.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
oUser, err := u.Conn.GetProfileByID(userId)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
fail.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
if in.Password != "" && !services.PasswordEqual(oUser.PasswordHash, in.Password) {
|
||||
in.PasswordHash, _ = services.GenerateHashFromPassword(in.Password)
|
||||
}
|
||||
|
||||
update := oUser.Merge(in.User)
|
||||
|
||||
if err := u.Conn.UpdateProfile(userId, update); err != nil {
|
||||
log.Println(err)
|
||||
fail.Payload = "Could not update profile"
|
||||
fail.Status = http.StatusOK
|
||||
fail.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := u.Conn.GetProfileByID(userId)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
fail.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
APIResp{Success: true, Payload: user}.Write(res)
|
||||
}
|
||||
|
||||
func (u UserHandler) getProfile(res http.ResponseWriter, req *http.Request) {
|
||||
userId, err := services.GetUserIDFromClaims(req)
|
||||
|
||||
fail := APIResp{Payload: "Unauthorized", Status: http.StatusUnauthorized}
|
||||
if err != nil {
|
||||
fail.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := u.Conn.GetProfileByID(userId)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
fail.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
APIResp{Success: true, Payload: user}.Write(res)
|
||||
}
|
||||
|
||||
func (u UserHandler) login(res http.ResponseWriter, req *http.Request) {
|
||||
type loginRequest struct {
|
||||
Email string `json:"email"`
|
||||
|
|
@ -46,7 +116,7 @@ func (u UserHandler) login(res http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
fail := APIResp{Payload: "Email or password incorrect"}
|
||||
fail := APIResp{Payload: "Email or password incorrect", Status: http.StatusUnauthorized}
|
||||
|
||||
if lr.Email == "" || lr.Password == "" {
|
||||
fail.Write(res)
|
||||
|
|
@ -61,13 +131,18 @@ func (u UserHandler) login(res http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
if services.PasswordEqual(profile.PasswordHash, lr.Password) {
|
||||
duration := u.JWTSessionDuration
|
||||
if duration == 0 {
|
||||
duration = time.Hour * 24 * 10
|
||||
}
|
||||
|
||||
token := services.GenerateJWT(profile)
|
||||
|
||||
res.Header().Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
http.SetCookie(res, &http.Cookie{
|
||||
Name: "user-auth",
|
||||
Name: "jwt",
|
||||
Value: token,
|
||||
Expires: time.Now().Add(time.Hour * 24 * 10),
|
||||
Expires: time.Now().Add(duration),
|
||||
})
|
||||
|
||||
APIResp{
|
||||
|
|
@ -82,28 +157,3 @@ func (u UserHandler) login(res http.ResponseWriter, req *http.Request) {
|
|||
|
||||
fail.Write(res)
|
||||
}
|
||||
|
||||
type APIResp struct {
|
||||
Success bool `json:"success"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
func (a APIResp) Write(res http.ResponseWriter) {
|
||||
res.Header().Set("Content-Type", "application/json; utf-8")
|
||||
enc := json.NewEncoder(res)
|
||||
enc.SetIndent("\n", "\t")
|
||||
enc.Encode(a)
|
||||
}
|
||||
|
||||
func ParseCall(model interface{}, res http.ResponseWriter, req *http.Request) error {
|
||||
defer req.Body.Close()
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&model); err != nil {
|
||||
a := APIResp{Success: false, Payload: err.Error()}
|
||||
a.Write(res)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package services
|
|||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
|
|
@ -34,3 +35,14 @@ func GenerateJWT(u User) string {
|
|||
_, tokenString, _ := tokenEncoder.Encode(map[string]interface{}{"id": u.ID})
|
||||
return tokenString
|
||||
}
|
||||
|
||||
func GetUserIDFromClaims(req *http.Request) (int64, error) {
|
||||
_, claims, _ := jwtauth.FromContext(req.Context())
|
||||
|
||||
userIdStr, ok := claims["id"]
|
||||
if !ok {
|
||||
return -1, errors.New("claim by name 'id' not found")
|
||||
}
|
||||
|
||||
return int64(userIdStr.(float64)), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
|
|
@ -12,8 +13,8 @@ var dbConn *sql.DB
|
|||
type DB interface {
|
||||
// VerifyEmail(email string) error
|
||||
// RegisterUser(email, username, password string) (User, error)
|
||||
// UpdateProfile(id int64, u User) error
|
||||
// GetProfile(id int64) (User, error)
|
||||
UpdateProfile(id int64, u User) error
|
||||
GetProfileByID(id int64) (User, error)
|
||||
GetProfileByEmail(email string) (User, error)
|
||||
}
|
||||
|
||||
|
|
@ -33,11 +34,63 @@ func Connect(host, user, password string) (DB, error) {
|
|||
|
||||
type pgdb struct{ *sql.DB }
|
||||
|
||||
func (db pgdb) UpdateProfile(id int64, u User) error {
|
||||
updateProfileSql := `
|
||||
UPDATE accounts.profile SET
|
||||
username = $2, displayunit = $3, birthdate = $4, height = $5
|
||||
WHERE userId = $1;
|
||||
`
|
||||
updateUserSql := `
|
||||
UPDATE accounts.users SET
|
||||
email = $2, passwordHash = $3
|
||||
WHERE id = $1;
|
||||
`
|
||||
tx, err := db.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit()
|
||||
|
||||
if _, err := tx.Exec(updateProfileSql, id, u.Username, u.DisplayUnit, u.Birthdate, u.Height); err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("could not update profile: %w", err)
|
||||
}
|
||||
if _, err := tx.Exec(updateUserSql, id, u.Email, u.PasswordHash); err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("could not update user: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db pgdb) GetProfileByID(id int64) (User, error) {
|
||||
sql := `SELECT
|
||||
u.id, u.email, u.passwordhash, p.username, p.displayunit, p.birthdate, p.height
|
||||
FROM Accounts.Users u JOIN Accounts.Profile p on u.id = p.userId
|
||||
WHERE u.id = $1 LIMIT 1;
|
||||
`
|
||||
var u User
|
||||
if err := db.QueryRow(sql, id).Scan(
|
||||
&u.ID,
|
||||
&u.Email,
|
||||
&u.PasswordHash,
|
||||
&u.Username,
|
||||
&u.DisplayUnit,
|
||||
&u.Birthdate,
|
||||
&u.Height,
|
||||
); err != nil {
|
||||
return u, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
|
||||
}
|
||||
|
||||
func (db pgdb) GetProfileByEmail(email string) (User, error) {
|
||||
sql := `SELECT
|
||||
u.id, u.email, u.passwordhash, p.username, p.displayunit, p.birthdate, p.height
|
||||
FROM Accounts.Users u JOIN Accounts.Profile p on u.id = p.userId
|
||||
WHERE u.email = $1;
|
||||
WHERE u.email = $1 LIMIT 1;
|
||||
`
|
||||
var u User
|
||||
if err := db.QueryRow(sql, email).Scan(
|
||||
|
|
|
|||
|
|
@ -18,3 +18,33 @@ type User struct {
|
|||
Height *float64 `json:"height"`
|
||||
PasswordHash string `json:"-"`
|
||||
}
|
||||
|
||||
func (u User) Merge(other User) User {
|
||||
nu := u
|
||||
|
||||
if other.Email != "" {
|
||||
nu.Email = other.Email
|
||||
}
|
||||
|
||||
if other.Username != "" {
|
||||
nu.Username = other.Username
|
||||
}
|
||||
|
||||
if other.PasswordHash != "" {
|
||||
nu.PasswordHash = other.PasswordHash
|
||||
}
|
||||
|
||||
if other.Birthdate != nil {
|
||||
nu.Birthdate = other.Birthdate
|
||||
}
|
||||
|
||||
if other.Height != nil && *other.Height != 0 {
|
||||
nu.Height = other.Height
|
||||
}
|
||||
|
||||
if other.DisplayUnit != "" {
|
||||
nu.DisplayUnit = other.DisplayUnit
|
||||
}
|
||||
|
||||
return nu
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue