add profile update

trunk
Adam Veldhousen 3 years ago
parent d40367a25d
commit b689c91d1b
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

@ -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.Get("/user", u.getProfile)
// r.Post("/user", u.updateProfile)
r.Use(services.JWTVerifier)
r.Use(jwtauth.Authenticator)
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…
Cancel
Save