add update users and refactor

This commit is contained in:
nochill 2023-10-09 16:25:39 +07:00
parent eb9774cce5
commit f8ecd20220
9 changed files with 269 additions and 83 deletions

View File

@ -67,8 +67,11 @@ func (server *Server) getRoutes() {
// REQUIRE AUTH TOKEN // REQUIRE AUTH TOKEN
authRoutes := router.Use(authMiddleware(server.TokenMaker)) authRoutes := router.Use(authMiddleware(server.TokenMaker))
authRoutes.POST("/review/location", server.createReview) authRoutes.POST("/review/location", server.createReview)
authRoutes.PATCH("/user", server.updateUser)
authRoutes.GET("/user/review/location/:location_id", server.getUserReviewByLocation) authRoutes.GET("/user/review/location/:location_id", server.getUserReviewByLocation)
authRoutes.GET("/user/profile", server.getUserStats) authRoutes.GET("/user/profile", server.getUserStats)
authRoutes.PATCH("/user/avatar", server.updateUserAvatar)
authRoutes.DELETE("/user/avatar", server.removeAvatar)
server.Router = router server.Router = router
} }

View File

@ -3,7 +3,10 @@ package api
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"os"
"path/filepath"
"time" "time"
db "git.nochill.in/nochill/hiling_go/db/sqlc" db "git.nochill.in/nochill/hiling_go/db/sqlc"
@ -11,6 +14,7 @@ import (
"git.nochill.in/nochill/hiling_go/util/token" "git.nochill.in/nochill/hiling_go/util/token"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/sqlc-dev/pqtype"
) )
type createUserRequest struct { type createUserRequest struct {
@ -161,6 +165,99 @@ func (server *Server) getUserStats(ctx *gin.Context) {
}) })
} }
type UpdateUserRequest struct {
About string `json:"about"`
Website string `json:"website"`
SocialMedia []map[string]string `json:"social_media"`
}
func (server *Server) updateUser(ctx *gin.Context) {
var req UpdateUserRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
fmt.Println(req.About)
social, _ := json.Marshal(req.SocialMedia)
socialArr := pqtype.NullRawMessage{
RawMessage: social,
Valid: len(req.SocialMedia) > 0,
}
user, err := server.Store.UpdateUser(ctx, db.UpdateUserParams{
About: sql.NullString{String: req.About, Valid: true},
SocialMedia: socialArr,
Website: sql.NullString{String: req.Website, Valid: len(req.Website) > 0},
ID: int32(authPayload.UserID),
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to update user"))
return
}
ctx.JSON(http.StatusOK, user)
}
func (server *Server) updateUserAvatar(ctx *gin.Context) {
file, err := ctx.FormFile("file")
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to parse file"))
return
}
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
fileExt := filepath.Ext(file.Filename)
now := time.Now()
dir := fmt.Sprintf("public/upload/images/user/%d/avatar", authPayload.UserID)
osFilename := fmt.Sprintf("%s%s%s", util.RandomString(5), fmt.Sprintf("%v", now.Unix()), fileExt)
imgPath := fmt.Sprintf("%s/%s", dir, osFilename)
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0775)
}
if err := ctx.SaveUploadedFile(file, imgPath); err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Error while try to save thumbnail image"))
return
}
url, err := server.Store.UpdateAvatar(ctx, db.UpdateAvatarParams{
ID: int32(authPayload.UserID),
AvatarPicture: sql.NullString{Valid: true, String: imgPath},
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to save image to database"))
return
}
ctx.JSON(http.StatusOK, gin.H{"image_url": url.String})
}
func (server *Server) removeAvatar(ctx *gin.Context) {
authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload)
_, err := server.Store.UpdateAvatar(ctx, db.UpdateAvatarParams{
AvatarPicture: sql.NullString{String: "", Valid: false},
ID: int32(authPayload.UserID),
})
if err != nil {
ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to update user avatar"))
return
}
ctx.Writer.WriteHeader(http.StatusNoContent)
}
func (server *Server) login(ctx *gin.Context) { func (server *Server) login(ctx *gin.Context) {
var req createUserRequest var req createUserRequest

View File

@ -6,6 +6,7 @@ package mockdb
import ( import (
context "context" context "context"
sql "database/sql"
reflect "reflect" reflect "reflect"
db "git.nochill.in/nochill/hiling_go/db/sqlc" db "git.nochill.in/nochill/hiling_go/db/sqlc"
@ -391,6 +392,21 @@ func (mr *MockStoreMockRecorder) RemoveFollowUser(arg0, arg1 interface{}) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveFollowUser", reflect.TypeOf((*MockStore)(nil).RemoveFollowUser), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveFollowUser", reflect.TypeOf((*MockStore)(nil).RemoveFollowUser), arg0, arg1)
} }
// UpdateAvatar mocks base method.
func (m *MockStore) UpdateAvatar(arg0 context.Context, arg1 db.UpdateAvatarParams) (sql.NullString, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAvatar", arg0, arg1)
ret0, _ := ret[0].(sql.NullString)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateAvatar indicates an expected call of UpdateAvatar.
func (mr *MockStoreMockRecorder) UpdateAvatar(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAvatar", reflect.TypeOf((*MockStore)(nil).UpdateAvatar), arg0, arg1)
}
// UpdateLocationThumbnail mocks base method. // UpdateLocationThumbnail mocks base method.
func (m *MockStore) UpdateLocationThumbnail(arg0 context.Context, arg1 db.UpdateLocationThumbnailParams) error { func (m *MockStore) UpdateLocationThumbnail(arg0 context.Context, arg1 db.UpdateLocationThumbnailParams) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -420,10 +436,10 @@ func (mr *MockStoreMockRecorder) UpdatePassword(arg0, arg1 interface{}) *gomock.
} }
// UpdateUser mocks base method. // UpdateUser mocks base method.
func (m *MockStore) UpdateUser(arg0 context.Context, arg1 db.UpdateUserParams) (db.User, error) { func (m *MockStore) UpdateUser(arg0 context.Context, arg1 db.UpdateUserParams) (db.UpdateUserRow, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser", arg0, arg1) ret := m.ctrl.Call(m, "UpdateUser", arg0, arg1)
ret0, _ := ret[0].(db.User) ret0, _ := ret[0].(db.UpdateUserRow)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }

View File

@ -5,17 +5,13 @@ INSERT INTO users (
) VALUES ($1, $2) ) VALUES ($1, $2)
RETURNING *; RETURNING *;
-- name: UpdateUser :one
UPDATE users
SET
email = COALESCE(sqlc.narg(email), email),
username = COALESCE(sqlc.narg(username), username),
avatar_picture = COALESCE(sqlc.narg(avatar_picture), avatar_picture)
WHERE
id = sqlc.arg(id)
RETURNING *;
-- name: UpdatePassword :exec -- name: UpdatePassword :exec
UPDATE users UPDATE users
SET password = $1 SET password = $1
WHERE id = $2; WHERE id = $2;
-- name: UpdateAvatar :one
UPDATE users
SET avatar_picture = $1
WHERE id = $2
RETURNING avatar_picture;

View File

@ -269,6 +269,8 @@ type User struct {
SocialMedia pqtype.NullRawMessage `json:"social_media"` SocialMedia pqtype.NullRawMessage `json:"social_media"`
CreatedAt sql.NullTime `json:"created_at"` CreatedAt sql.NullTime `json:"created_at"`
UpdatedAt sql.NullTime `json:"updated_at"` UpdatedAt sql.NullTime `json:"updated_at"`
About sql.NullString `json:"about"`
Website sql.NullString `json:"website"`
} }
type UserActivity struct { type UserActivity struct {

View File

@ -6,6 +6,7 @@ package db
import ( import (
"context" "context"
"database/sql"
) )
type Querier interface { type Querier interface {
@ -23,9 +24,9 @@ type Querier interface {
GetSession(ctx context.Context, id int32) (UserSession, error) GetSession(ctx context.Context, id int32) (UserSession, error)
GetUserReviewByLocation(ctx context.Context, arg GetUserReviewByLocationParams) (GetUserReviewByLocationRow, error) GetUserReviewByLocation(ctx context.Context, arg GetUserReviewByLocationParams) (GetUserReviewByLocationRow, error)
RemoveFollowUser(ctx context.Context, arg RemoveFollowUserParams) error RemoveFollowUser(ctx context.Context, arg RemoveFollowUserParams) error
UpdateAvatar(ctx context.Context, arg UpdateAvatarParams) (sql.NullString, error)
UpdateLocationThumbnail(ctx context.Context, arg UpdateLocationThumbnailParams) error UpdateLocationThumbnail(ctx context.Context, arg UpdateLocationThumbnailParams) error
UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
} }
var _ Querier = (*Queries)(nil) var _ Querier = (*Queries)(nil)

View File

@ -14,6 +14,7 @@ type Store interface {
GetImagesByLocation(ctx context.Context, arg GetImagesByLocationParams) ([]GetImagesByLocationRow, error) GetImagesByLocation(ctx context.Context, arg GetImagesByLocationParams) ([]GetImagesByLocationRow, error)
GetLocation(ctx context.Context, location_id int32) (GetLocationRow, error) GetLocation(ctx context.Context, location_id int32) (GetLocationRow, error)
GetUser(ctx context.Context, username string) (GetUserRow, error) GetUser(ctx context.Context, username string) (GetUserRow, error)
UpdateUser(ctx context.Context, arg UpdateUserParams) (UpdateUserRow, error)
GetUserStats(ctx context.Context, user_id int32) (GetUserStatsRow, error) GetUserStats(ctx context.Context, user_id int32) (GetUserStatsRow, error)
CreateReview(ctx context.Context, arg CreateReviewParams) (Review, error) CreateReview(ctx context.Context, arg CreateReviewParams) (Review, error)
GetListLocationReviews(ctx context.Context, arg GetListLocationReviewsParams) ([]GetListLocationReviewsRow, error) GetListLocationReviews(ctx context.Context, arg GetListLocationReviewsParams) ([]GetListLocationReviewsRow, error)

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "time"
"github.com/sqlc-dev/pqtype" "github.com/sqlc-dev/pqtype"
) )
@ -15,6 +15,9 @@ SELECT
COALESCE(email, '') as email, COALESCE(email, '') as email,
password, password,
username, username,
COALESCE(google_sign_in_payload, '') as google_sign_in_payload,
COALESCE(about, '') as about,
COALESCE(website, '') as website,
COALESCE(avatar_picture, '') as avatar_picture, COALESCE(avatar_picture, '') as avatar_picture,
banned_at, banned_at,
banned_until, banned_until,
@ -23,25 +26,32 @@ SELECT
is_admin, is_admin,
is_critics, is_critics,
is_verified, is_verified,
social_media COALESCE(social_media, '[]'),
created_at,
updated_at
FROM USERS FROM USERS
WHERE username = $1 WHERE username = $1
` `
type GetUserRow struct { type GetUserRow struct {
ID int32 `json:"id"` ID int32 `json:"id"`
Email string `json:"email"` Email string `json:"email"`
Password string `json:"-"` Password string `json:"-"`
Username string `json:"username"` About string `json:"about"`
AvatarPicture string `json:"avatar_picture"` Website string `json:"website"`
BannedAt sql.NullTime `json:"banned_at"` Username string `json:"username"`
BannedUntil sql.NullTime `json:"banned_until"` GoogleSignInPayload string `json:"google_sign_in_payload"`
BanReason string `json:"ban_reason"` AvatarPicture string `json:"avatar_picture"`
IsPermaban bool `json:"is_permaban"` BannedAt sql.NullTime `json:"banned_at"`
IsAdmin bool `json:"is_admin"` BannedUntil sql.NullTime `json:"banned_until"`
IsCritics bool `json:"is_critics"` BanReason string `json:"ban_reason"`
IsVerified bool `json:"is_verified"` IsPermaban bool `json:"is_permaban"`
SocialMedia pqtype.NullRawMessage `json:"social_media"` IsAdmin bool `json:"is_admin"`
IsCritics bool `json:"is_critics"`
IsVerified bool `json:"is_verified"`
SocialMedia json.RawMessage `json:"social_media"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
} }
func (q *Queries) GetUser(ctx context.Context, username string) (GetUserRow, error) { func (q *Queries) GetUser(ctx context.Context, username string) (GetUserRow, error) {
@ -52,6 +62,9 @@ func (q *Queries) GetUser(ctx context.Context, username string) (GetUserRow, err
&i.Email, &i.Email,
&i.Password, &i.Password,
&i.Username, &i.Username,
&i.GoogleSignInPayload,
&i.About,
&i.Website,
&i.AvatarPicture, &i.AvatarPicture,
&i.BannedAt, &i.BannedAt,
&i.BannedUntil, &i.BannedUntil,
@ -61,6 +74,8 @@ func (q *Queries) GetUser(ctx context.Context, username string) (GetUserRow, err
&i.IsCritics, &i.IsCritics,
&i.IsVerified, &i.IsVerified,
&i.SocialMedia, &i.SocialMedia,
&i.CreatedAt,
&i.UpdatedAt,
) )
return i, err return i, err
} }
@ -126,12 +141,94 @@ func (q *Queries) GetUserStats(ctx context.Context, user_id int32) (GetUserStats
&i.ScoreCount, &i.ScoreCount,
&i.ScoresDistribution, &i.ScoresDistribution,
) )
var r []map[string]any
err = json.Unmarshal(i.ScoresDistribution, &r)
fmt.Println(r)
return i, err return i, err
} }
const updateUser = `-- name: UpdateUser :one
UPDATE users
SET
about = $1,
social_media = $2,
website = $3
WHERE
id = $4
RETURNING
id,
COALESCE(email, ''),
username,
COALESCE(avatar_picture, ''),
COALESCE(about, ''),
COALESCE(website, ''),
COALESCE(google_sign_in_payload, ''),
banned_at,
banned_until,
COALESCE(ban_reason, ''),
is_permaban,
is_admin,
is_critics,
is_verified,
is_active,
COALESCE(social_media, '[]'),
created_at,
updated_at
`
type UpdateUserParams struct {
About sql.NullString `json:"about"`
SocialMedia pqtype.NullRawMessage `json:"social_media"`
Website sql.NullString `json:"website"`
ID int32 `json:"id"`
}
type UpdateUserRow struct {
ID int32 `json:"id"`
Email string `json:"email"`
Username string `json:"username"`
AvatarPicture string `json:"avatar_picture"`
About string `json:"about"`
Website string `json:"website"`
GoogleSignInPayload string `json:"google_sign_in_payload"`
BannedAt sql.NullTime `json:"banned_at"`
BannedUntil sql.NullTime `json:"banned_until"`
BanReason string `json:"ban_reason"`
IsPermaban bool `json:"is_permaban"`
IsAdmin bool `json:"is_admin"`
IsCritics bool `json:"is_critics"`
IsVerified bool `json:"is_verified"`
IsActive bool `json:"is_active"`
SocialMedia json.RawMessage `json:"social_media"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (UpdateUserRow, error) {
row := q.db.QueryRowContext(ctx, updateUser,
arg.About,
arg.SocialMedia,
arg.Website,
arg.ID,
)
var i UpdateUserRow
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.AvatarPicture,
&i.About,
&i.Website,
&i.GoogleSignInPayload,
&i.BannedAt,
&i.BannedUntil,
&i.BanReason,
&i.IsPermaban,
&i.IsAdmin,
&i.IsCritics,
&i.IsVerified,
&i.IsActive,
&i.SocialMedia,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@ -15,7 +15,7 @@ INSERT INTO users (
username, username,
password password
) VALUES ($1, $2) ) VALUES ($1, $2)
RETURNING id, email, username, password, avatar_picture, google_sign_in_payload, banned_at, banned_until, ban_reason, is_permaban, is_admin, is_critics, is_verified, is_active, social_media, created_at, updated_at RETURNING id, email, username, password, avatar_picture, google_sign_in_payload, banned_at, banned_until, ban_reason, is_permaban, is_admin, is_critics, is_verified, is_active, social_media, created_at, updated_at, about, website
` `
type CreateUserParams struct { type CreateUserParams struct {
@ -44,10 +44,31 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
&i.SocialMedia, &i.SocialMedia,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.About,
&i.Website,
) )
return i, err return i, err
} }
const updateAvatar = `-- name: UpdateAvatar :one
UPDATE users
SET avatar_picture = $1
WHERE id = $2
RETURNING avatar_picture
`
type UpdateAvatarParams struct {
AvatarPicture sql.NullString `json:"avatar_picture"`
ID int32 `json:"id"`
}
func (q *Queries) UpdateAvatar(ctx context.Context, arg UpdateAvatarParams) (sql.NullString, error) {
row := q.db.QueryRowContext(ctx, updateAvatar, arg.AvatarPicture, arg.ID)
var avatar_picture sql.NullString
err := row.Scan(&avatar_picture)
return avatar_picture, err
}
const updatePassword = `-- name: UpdatePassword :exec const updatePassword = `-- name: UpdatePassword :exec
UPDATE users UPDATE users
SET password = $1 SET password = $1
@ -63,51 +84,3 @@ func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams)
_, err := q.db.ExecContext(ctx, updatePassword, arg.Password, arg.ID) _, err := q.db.ExecContext(ctx, updatePassword, arg.Password, arg.ID)
return err return err
} }
const updateUser = `-- name: UpdateUser :one
UPDATE users
SET
email = COALESCE($1, email),
username = COALESCE($2, username),
avatar_picture = COALESCE($3, avatar_picture)
WHERE
id = $4
RETURNING id, email, username, password, avatar_picture, google_sign_in_payload, banned_at, banned_until, ban_reason, is_permaban, is_admin, is_critics, is_verified, is_active, social_media, created_at, updated_at
`
type UpdateUserParams struct {
Email sql.NullString `json:"email"`
Username sql.NullString `json:"username"`
AvatarPicture sql.NullString `json:"avatar_picture"`
ID int32 `json:"id"`
}
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error) {
row := q.db.QueryRowContext(ctx, updateUser,
arg.Email,
arg.Username,
arg.AvatarPicture,
arg.ID,
)
var i User
err := row.Scan(
&i.ID,
&i.Email,
&i.Username,
&i.Password,
&i.AvatarPicture,
&i.GoogleSignInPayload,
&i.BannedAt,
&i.BannedUntil,
&i.BanReason,
&i.IsPermaban,
&i.IsAdmin,
&i.IsCritics,
&i.IsVerified,
&i.IsActive,
&i.SocialMedia,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}