package api import ( "encoding/json" "fmt" "net/http" "os" "path/filepath" "time" db "git.nochill.in/nochill/hiling_go/db/sqlc" "git.nochill.in/nochill/hiling_go/util" "git.nochill.in/nochill/hiling_go/util/token" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) type createUserRequest struct { Username string `json:"username" binding:"required,alphanum"` Password string `json:"password" binding:"required,min=7"` } type createUserResponse struct { ID int32 `json:"id"` Username string `json:"username"` AvatarPicture string `json:"avatar_picture"` // avatar_url BannedAt pgtype.Timestamp `json:"banned_at"` BannedUntil pgtype.Timestamp `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"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (server *Server) createUser(ctx *gin.Context) { var req createUserRequest if err := ctx.ShouldBindJSON(&req); err != nil { if err != nil { ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err)) return } } hashedPassword, err := util.HashPassword(req.Password) if err != nil { ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong")) return } arg := db.CreateUserParams{ Username: req.Username, Password: hashedPassword, } user, err := server.Store.CreateUser(ctx, arg) if err != nil { if db.ErrorCode(err) == db.UniqueViolation { ctx.JSON(http.StatusConflict, ErrorResponse(err, "Username already used")) } ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong")) return } accessToken, _, err := server.TokenMaker.CreateToken( user.Username, int(user.ID), server.Config.TokenDuration, ) if err != nil { ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while creating token")) return } refreshToken, refreshTokenPayload, err := server.TokenMaker.CreateToken( user.Username, int(user.ID), server.Config.RefreshTokenDuration, ) _, err = server.Store.CreateSession(ctx, db.CreateSessionParams{ Username: user.Username, RefreshToken: refreshToken, UserAgent: ctx.Request.UserAgent(), ClientIp: ctx.ClientIP(), IsBlocked: false, ExpiresAt: pgtype.Timestamp{Valid: true, Time: refreshTokenPayload.ExpiredAt}, }) if err != nil { ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while saving sessions")) return } res := createUserResponse{ ID: user.ID, Username: user.Username, AvatarPicture: user.AvatarPicture.String, BannedAt: pgtype.Timestamp{Valid: user.BannedAt.Valid, Time: user.BannedAt.Time}, BannedUntil: pgtype.Timestamp{Valid: user.BannedUntil.Valid, Time: user.BannedUntil.Time}, BanReason: user.BanReason.String, IsPermaban: user.IsPermaban.Bool, IsAdmin: user.IsAdmin.Bool, IsCritics: user.IsCritics.Bool, IsVerified: user.IsVerified.Bool, CreatedAt: user.CreatedAt.Time, UpdatedAt: user.UpdatedAt.Time, } ctx.SetCookie( "paseto", accessToken, int(server.Config.CookieDuration), "/", "localhost", false, false, ) ctx.JSON(http.StatusOK, res) } func (server *Server) getUserStats(ctx *gin.Context) { var scoreDistribution []map[string]int8 authPayload := ctx.MustGet(authorizationPayloadKey).(*token.Payload) userStats, err := server.Store.GetUserStats(ctx, int32(authPayload.UserID)) if err != nil { ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to get user stats")) return } err = json.Unmarshal(userStats.ScoresDistribution, &scoreDistribution) if err != nil { ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong whilet try to parse score distribution")) return } var userReviews []map[string]any if userStats.Reviews != nil { err = json.Unmarshal(userStats.Reviews, &userReviews) if err != nil { ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something wrong while try to parse user reviwes stats")) return } } else { userReviews = make([]map[string]any, 0) } ctx.JSON(http.StatusOK, gin.H{ "reviews": userReviews, "scores_distribution": scoreDistribution, "user_stats": userStats, }) } type UpdateUserRequest struct { About string `json:"about"` Website string `json:"website"` SocialMedia interface{} `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) user, err := server.Store.UpdateUser(ctx, db.UpdateUserParams{ About: pgtype.Text{String: req.About, Valid: true}, SocialMedia: req.SocialMedia, Website: pgtype.Text{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 } // // user.BannedAt = util.ParseNullDateTime() 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: pgtype.Text{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: pgtype.Text{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) { var req createUserRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(http.StatusBadRequest, ValidationErrorResponse(err)) return } user, err := server.Store.GetUser(ctx, req.Username) if err != nil { if err == pgx.ErrNoRows { ctx.JSON(http.StatusNotFound, ErrorResponse(err, "User not found")) return } ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong whlie try to get user")) return } err = util.CheckPassword(req.Password, user.Password) if err != nil { ctx.JSON(http.StatusUnauthorized, ErrorResponse(err, "Password not match")) return } accessToken, _, err := server.TokenMaker.CreateToken(user.Username, int(user.ID), server.Config.TokenDuration) if err != nil { ctx.JSON(http.StatusInternalServerError, "Something went wrong while try to create token") return } refreshToken, refreshTokenPayload, err := server.TokenMaker.CreateToken( user.Username, int(user.ID), server.Config.RefreshTokenDuration, ) _, err = server.Store.CreateSession(ctx, db.CreateSessionParams{ Username: user.Username, UserAgent: ctx.Request.UserAgent(), ClientIp: ctx.ClientIP(), RefreshToken: refreshToken, ExpiresAt: pgtype.Timestamp{Valid: true, Time: refreshTokenPayload.ExpiredAt}, IsBlocked: false, }) if err != nil { ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong while try to create session")) return } ctx.SetCookie( "paseto", accessToken, int(server.Config.CookieDuration), "/", "localhost", false, false, ) ctx.JSON(http.StatusOK, user) } func (server *Server) logout(ctx *gin.Context) { ctx.SetCookie( "paseto", "", -1, "/", "", false, true, ) ctx.Writer.WriteHeader(http.StatusNoContent) }