minimal registeration implementation
This commit is contained in:
parent
19ac718526
commit
6002b594c6
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -20,4 +20,5 @@
|
||||||
"**/internal/ports/**/*.go": "${filename}.ports",
|
"**/internal/ports/**/*.go": "${filename}.ports",
|
||||||
"**/internal/web_server/handlers/**/*.go": "${filename}.handlers",
|
"**/internal/web_server/handlers/**/*.go": "${filename}.handlers",
|
||||||
},
|
},
|
||||||
|
"makefile.configureOnOpen": false,
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,13 @@ FROM users
|
||||||
WHERE user_name = $1
|
WHERE user_name = $1
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- name: IsProfileCompleted :one
|
||||||
|
SELECT
|
||||||
|
CASE WHEN profile_completed = true THEN true ELSE false END AS is_pending
|
||||||
|
FROM users
|
||||||
|
WHERE id = $1
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: IsUserNameUnique :one
|
-- name: IsUserNameUnique :one
|
||||||
SELECT
|
SELECT
|
||||||
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
||||||
|
|
|
||||||
|
|
@ -652,6 +652,21 @@ func (q *Queries) GetUserByUserName(ctx context.Context, userName string) (GetUs
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IsProfileCompleted = `-- name: IsProfileCompleted :one
|
||||||
|
SELECT
|
||||||
|
CASE WHEN profile_completed = true THEN true ELSE false END AS is_pending
|
||||||
|
FROM users
|
||||||
|
WHERE id = $1
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) IsProfileCompleted(ctx context.Context, id int64) (bool, error) {
|
||||||
|
row := q.db.QueryRow(ctx, IsProfileCompleted, id)
|
||||||
|
var is_pending bool
|
||||||
|
err := row.Scan(&is_pending)
|
||||||
|
return is_pending, err
|
||||||
|
}
|
||||||
|
|
||||||
const IsUserNameUnique = `-- name: IsUserNameUnique :one
|
const IsUserNameUnique = `-- name: IsUserNameUnique :one
|
||||||
SELECT
|
SELECT
|
||||||
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
||||||
|
|
|
||||||
|
|
@ -125,18 +125,18 @@ type RegisterUserReq struct {
|
||||||
|
|
||||||
OtpMedium OtpMedium `json:"otp_medium"`
|
OtpMedium OtpMedium `json:"otp_medium"`
|
||||||
|
|
||||||
NickName string `json:"nick_name,omitempty"`
|
// NickName string `json:"nick_name,omitempty"`
|
||||||
Occupation string `json:"occupation,omitempty"`
|
// Occupation string `json:"occupation,omitempty"`
|
||||||
LearningGoal string `json:"learning_goal,omitempty"`
|
// LearningGoal string `json:"learning_goal,omitempty"`
|
||||||
LanguageGoal string `json:"language_goal,omitempty"`
|
// LanguageGoal string `json:"language_goal,omitempty"`
|
||||||
LanguageChallange string `json:"language_challange,omitempty"`
|
// LanguageChallange string `json:"language_challange,omitempty"`
|
||||||
FavoutiteTopic string `json:"favoutite_topic,omitempty"`
|
// FavoutiteTopic string `json:"favoutite_topic,omitempty"`
|
||||||
|
|
||||||
Age int `json:"age,omitempty"`
|
// Age int `json:"age,omitempty"`
|
||||||
EducationLevel string `json:"education_level,omitempty"`
|
// EducationLevel string `json:"education_level,omitempty"`
|
||||||
Country string `json:"country,omitempty"`
|
// Country string `json:"country,omitempty"`
|
||||||
Region string `json:"region,omitempty"`
|
// Region string `json:"region,omitempty"`
|
||||||
PreferredLanguage string `json:"preferred_language,omitempty"`
|
// PreferredLanguage string `json:"preferred_language,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUserReq struct {
|
type CreateUserReq struct {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserStore interface {
|
type UserStore interface {
|
||||||
|
IsProfileCompleted(ctx context.Context, userId int64) (bool, error)
|
||||||
UpdateUserStatus(ctx context.Context, user domain.UpdateUserReq) error
|
UpdateUserStatus(ctx context.Context, user domain.UpdateUserReq) error
|
||||||
// GetCorrectOptionForQuestion(
|
// GetCorrectOptionForQuestion(
|
||||||
// ctx context.Context,
|
// ctx context.Context,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,17 @@ import (
|
||||||
|
|
||||||
func NewUserStore(s *Store) ports.UserStore { return s }
|
func NewUserStore(s *Store) ports.UserStore { return s }
|
||||||
|
|
||||||
|
func (s *Store) IsProfileCompleted(ctx context.Context, userId int64) (bool, error) {
|
||||||
|
IsProfileCompleted, err := s.queries.IsProfileCompleted(ctx, userId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return false, authentication.ErrUserNotFound
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return IsProfileCompleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) UpdateUserKnowledgeLevel(ctx context.Context, userID int64, knowledgeLevel string) error {
|
func (s *Store) UpdateUserKnowledgeLevel(ctx context.Context, userID int64, knowledgeLevel string) error {
|
||||||
return s.queries.UpdateUserKnowledgeLevel(ctx, dbgen.UpdateUserKnowledgeLevelParams{
|
return s.queries.UpdateUserKnowledgeLevel(ctx, dbgen.UpdateUserKnowledgeLevelParams{
|
||||||
ID: userID,
|
ID: userID,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserStore interface {
|
type UserStore interface {
|
||||||
|
IsProfileCompleted(ctx context.Context, userId int64) (bool, error)
|
||||||
UpdateUserKnowledgeLevel(ctx context.Context, userID int64, knowledgeLevel string) error
|
UpdateUserKnowledgeLevel(ctx context.Context, userID int64, knowledgeLevel string) error
|
||||||
IsUserPending(ctx context.Context, userName string) (bool, error)
|
IsUserPending(ctx context.Context, userName string) (bool, error)
|
||||||
GetUserByUserName(
|
GetUserByUserName(
|
||||||
|
|
|
||||||
|
|
@ -63,21 +63,21 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU
|
||||||
Role: domain.RoleStudent,
|
Role: domain.RoleStudent,
|
||||||
EmailVerified: false,
|
EmailVerified: false,
|
||||||
PhoneVerified: false,
|
PhoneVerified: false,
|
||||||
EducationLevel: registerReq.EducationLevel,
|
// EducationLevel: registerReq.EducationLevel,
|
||||||
Age: registerReq.Age,
|
// Age: registerReq.Age,
|
||||||
Country: registerReq.Country,
|
// Country: registerReq.Country,
|
||||||
Region: registerReq.Region,
|
// Region: registerReq.Region,
|
||||||
Status: domain.UserStatusPending,
|
Status: domain.UserStatusPending,
|
||||||
ProfileCompleted: false,
|
ProfileCompleted: false,
|
||||||
PreferredLanguage: registerReq.PreferredLanguage,
|
// PreferredLanguage: registerReq.PreferredLanguage,
|
||||||
|
|
||||||
// Optional fields
|
// Optional fields
|
||||||
NickName: registerReq.NickName,
|
// NickName: registerReq.NickName,
|
||||||
Occupation: registerReq.Occupation,
|
// Occupation: registerReq.Occupation,
|
||||||
LearningGoal: registerReq.LearningGoal,
|
// LearningGoal: registerReq.LearningGoal,
|
||||||
LanguageGoal: registerReq.LanguageGoal,
|
// LanguageGoal: registerReq.LanguageGoal,
|
||||||
LanguageChallange: registerReq.LanguageChallange,
|
// LanguageChallange: registerReq.LanguageChallange,
|
||||||
FavoutiteTopic: registerReq.FavoutiteTopic,
|
// FavoutiteTopic: registerReq.FavoutiteTopic,
|
||||||
// ProfilePictureURL: registerReq.ProfilePictureURL,
|
// ProfilePictureURL: registerReq.ProfilePictureURL,
|
||||||
|
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ func (s *Service) IsUserPending(ctx context.Context, userName string) (bool, err
|
||||||
return s.userStore.IsUserPending(ctx, userName)
|
return s.userStore.IsUserPending(ctx, userName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) IsProfileCompleted(ctx context.Context, userId int64) (bool, error) {
|
||||||
|
return s.userStore.IsProfileCompleted(ctx, userId)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) IsUserNameUnique(ctx context.Context, userName string) (bool, error) {
|
func (s *Service) IsUserNameUnique(ctx context.Context, userName string) (bool, error) {
|
||||||
return s.userStore.IsUserNameUnique(ctx, userName)
|
return s.userStore.IsUserNameUnique(ctx, userName)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,58 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CheckProfileCompleted godoc
|
||||||
|
// @Summary Check if user profile is completed
|
||||||
|
// @Description Returns whether the specified user's profile is completed
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param user_id path int true "User ID"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 404 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/user/{user_id}/is-profile-completed [get]
|
||||||
|
func (h *Handler) CheckProfileCompleted(c *fiber.Ctx) error {
|
||||||
|
userIDParam := c.Params("user_id")
|
||||||
|
if userIDParam == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid user id",
|
||||||
|
Error: "User id cannot be empty",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := strconv.ParseInt(userIDParam, 10, 64)
|
||||||
|
if err != nil || userID <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid user id",
|
||||||
|
Error: "User id must be a valid positive integer",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
isCompleted, err := h.userSvc.IsProfileCompleted(c.Context(), userID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, authentication.ErrUserNotFound) {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||||
|
Message: "User not found",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to check profile completion status",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Profile completion status fetched successfully",
|
||||||
|
Data: map[string]bool{
|
||||||
|
"is_profile_completed": isCompleted,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateUser godoc
|
// UpdateUser godoc
|
||||||
// @Summary Update user profile
|
// @Summary Update user profile
|
||||||
// @Description Updates user profile information (partial updates supported)
|
// @Description Updates user profile information (partial updates supported)
|
||||||
|
|
@ -741,26 +793,26 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
user := domain.RegisterUserReq{
|
user := domain.RegisterUserReq{
|
||||||
FirstName: req.FirstName,
|
FirstName: req.FirstName,
|
||||||
LastName: req.LastName,
|
LastName: req.LastName,
|
||||||
UserName: req.UserName,
|
UserName: req.UserName,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
PhoneNumber: req.PhoneNumber,
|
PhoneNumber: req.PhoneNumber,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
OtpMedium: domain.OtpMediumEmail,
|
OtpMedium: domain.OtpMediumEmail,
|
||||||
Role: string(domain.RoleStudent),
|
// Role: string(domain.RoleStudent),
|
||||||
Age: req.Age,
|
// Age: req.Age,
|
||||||
EducationLevel: req.EducationLevel,
|
// EducationLevel: req.EducationLevel,
|
||||||
Country: req.Country,
|
// Country: req.Country,
|
||||||
Region: req.Region,
|
// Region: req.Region,
|
||||||
PreferredLanguage: req.PreferredLanguage,
|
// PreferredLanguage: req.PreferredLanguage,
|
||||||
|
|
||||||
NickName: req.NickName,
|
// NickName: req.NickName,
|
||||||
Occupation: req.Occupation,
|
// Occupation: req.Occupation,
|
||||||
LearningGoal: req.LearningGoal,
|
// LearningGoal: req.LearningGoal,
|
||||||
LanguageGoal: req.LanguageGoal,
|
// LanguageGoal: req.LanguageGoal,
|
||||||
LanguageChallange: req.LanguageChallange,
|
// LanguageChallange: req.LanguageChallange,
|
||||||
FavoutiteTopic: req.FavoutiteTopic,
|
// FavoutiteTopic: req.FavoutiteTopic,
|
||||||
}
|
}
|
||||||
|
|
||||||
medium, err := getMedium(req.Email, req.PhoneNumber)
|
medium, err := getMedium(req.Email, req.PhoneNumber)
|
||||||
|
|
@ -803,25 +855,25 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
||||||
|
|
||||||
func MapRegisterReqToUser(req domain.RegisterUserReq) domain.User {
|
func MapRegisterReqToUser(req domain.RegisterUserReq) domain.User {
|
||||||
return domain.User{
|
return domain.User{
|
||||||
FirstName: req.FirstName,
|
FirstName: req.FirstName,
|
||||||
LastName: req.LastName,
|
LastName: req.LastName,
|
||||||
UserName: req.UserName,
|
UserName: req.UserName,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
PhoneNumber: req.PhoneNumber,
|
PhoneNumber: req.PhoneNumber,
|
||||||
Password: []byte(req.Password), // or hashed password
|
Password: []byte(req.Password), // or hashed password
|
||||||
Role: domain.Role(req.Role),
|
Role: domain.Role(req.Role),
|
||||||
Age: req.Age,
|
// Age: req.Age,
|
||||||
EducationLevel: req.EducationLevel,
|
// EducationLevel: req.EducationLevel,
|
||||||
Country: req.Country,
|
// Country: req.Country,
|
||||||
Region: req.Region,
|
// Region: req.Region,
|
||||||
PreferredLanguage: req.PreferredLanguage,
|
// PreferredLanguage: req.PreferredLanguage,
|
||||||
|
|
||||||
NickName: req.NickName,
|
// NickName: req.NickName,
|
||||||
Occupation: req.Occupation,
|
// Occupation: req.Occupation,
|
||||||
LearningGoal: req.LearningGoal,
|
// LearningGoal: req.LearningGoal,
|
||||||
LanguageGoal: req.LanguageGoal,
|
// LanguageGoal: req.LanguageGoal,
|
||||||
LanguageChallange: req.LanguageChallange,
|
// LanguageChallange: req.LanguageChallange,
|
||||||
FavoutiteTopic: req.FavoutiteTopic,
|
// FavoutiteTopic: req.FavoutiteTopic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ func (a *App) initAppRoutes() {
|
||||||
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
|
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
|
||||||
|
|
||||||
// User Routes
|
// User Routes
|
||||||
|
groupV1.Get("/user/:user_id/is-profile-completed", a.authMiddleware, h.CheckProfileCompleted)
|
||||||
groupV1.Get("/users", a.authMiddleware, h.GetAllUsers)
|
groupV1.Get("/users", a.authMiddleware, h.GetAllUsers)
|
||||||
groupV1.Put("/user", a.authMiddleware, h.UpdateUser)
|
groupV1.Put("/user", a.authMiddleware, h.UpdateUser)
|
||||||
groupV1.Put("/user/knowledge-level", h.UpdateUserKnowledgeLevel)
|
groupV1.Put("/user/knowledge-level", h.UpdateUserKnowledgeLevel)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user