minimal registeration implementation

This commit is contained in:
Yared Yemane 2026-01-09 06:35:22 -08:00
parent 19ac718526
commit 6002b594c6
11 changed files with 152 additions and 59 deletions

View File

@ -20,4 +20,5 @@
"**/internal/ports/**/*.go": "${filename}.ports",
"**/internal/web_server/handlers/**/*.go": "${filename}.handlers",
},
"makefile.configureOnOpen": false,
}

View File

@ -5,6 +5,13 @@ FROM users
WHERE user_name = $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
SELECT
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique

View File

@ -652,6 +652,21 @@ func (q *Queries) GetUserByUserName(ctx context.Context, userName string) (GetUs
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
SELECT
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique

View File

@ -125,18 +125,18 @@ type RegisterUserReq struct {
OtpMedium OtpMedium `json:"otp_medium"`
NickName string `json:"nick_name,omitempty"`
Occupation string `json:"occupation,omitempty"`
LearningGoal string `json:"learning_goal,omitempty"`
LanguageGoal string `json:"language_goal,omitempty"`
LanguageChallange string `json:"language_challange,omitempty"`
FavoutiteTopic string `json:"favoutite_topic,omitempty"`
// NickName string `json:"nick_name,omitempty"`
// Occupation string `json:"occupation,omitempty"`
// LearningGoal string `json:"learning_goal,omitempty"`
// LanguageGoal string `json:"language_goal,omitempty"`
// LanguageChallange string `json:"language_challange,omitempty"`
// FavoutiteTopic string `json:"favoutite_topic,omitempty"`
Age int `json:"age,omitempty"`
EducationLevel string `json:"education_level,omitempty"`
Country string `json:"country,omitempty"`
Region string `json:"region,omitempty"`
PreferredLanguage string `json:"preferred_language,omitempty"`
// Age int `json:"age,omitempty"`
// EducationLevel string `json:"education_level,omitempty"`
// Country string `json:"country,omitempty"`
// Region string `json:"region,omitempty"`
// PreferredLanguage string `json:"preferred_language,omitempty"`
}
type CreateUserReq struct {

View File

@ -8,6 +8,7 @@ import (
)
type UserStore interface {
IsProfileCompleted(ctx context.Context, userId int64) (bool, error)
UpdateUserStatus(ctx context.Context, user domain.UpdateUserReq) error
// GetCorrectOptionForQuestion(
// ctx context.Context,

View File

@ -16,6 +16,17 @@ import (
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 {
return s.queries.UpdateUserKnowledgeLevel(ctx, dbgen.UpdateUserKnowledgeLevelParams{
ID: userID,

View File

@ -6,6 +6,7 @@ import (
)
type UserStore interface {
IsProfileCompleted(ctx context.Context, userId int64) (bool, error)
UpdateUserKnowledgeLevel(ctx context.Context, userID int64, knowledgeLevel string) error
IsUserPending(ctx context.Context, userName string) (bool, error)
GetUserByUserName(

View File

@ -63,21 +63,21 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU
Role: domain.RoleStudent,
EmailVerified: false,
PhoneVerified: false,
EducationLevel: registerReq.EducationLevel,
Age: registerReq.Age,
Country: registerReq.Country,
Region: registerReq.Region,
// EducationLevel: registerReq.EducationLevel,
// Age: registerReq.Age,
// Country: registerReq.Country,
// Region: registerReq.Region,
Status: domain.UserStatusPending,
ProfileCompleted: false,
PreferredLanguage: registerReq.PreferredLanguage,
// PreferredLanguage: registerReq.PreferredLanguage,
// Optional fields
NickName: registerReq.NickName,
Occupation: registerReq.Occupation,
LearningGoal: registerReq.LearningGoal,
LanguageGoal: registerReq.LanguageGoal,
LanguageChallange: registerReq.LanguageChallange,
FavoutiteTopic: registerReq.FavoutiteTopic,
// NickName: registerReq.NickName,
// Occupation: registerReq.Occupation,
// LearningGoal: registerReq.LearningGoal,
// LanguageGoal: registerReq.LanguageGoal,
// LanguageChallange: registerReq.LanguageChallange,
// FavoutiteTopic: registerReq.FavoutiteTopic,
// ProfilePictureURL: registerReq.ProfilePictureURL,
CreatedAt: time.Now(),

View File

@ -18,6 +18,10 @@ func (s *Service) IsUserPending(ctx context.Context, userName string) (bool, err
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) {
return s.userStore.IsUserNameUnique(ctx, userName)
}

View File

@ -13,6 +13,58 @@ import (
"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
// @Summary Update user profile
// @Description Updates user profile information (partial updates supported)
@ -748,19 +800,19 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
PhoneNumber: req.PhoneNumber,
Password: req.Password,
OtpMedium: domain.OtpMediumEmail,
Role: string(domain.RoleStudent),
Age: req.Age,
EducationLevel: req.EducationLevel,
Country: req.Country,
Region: req.Region,
PreferredLanguage: req.PreferredLanguage,
// Role: string(domain.RoleStudent),
// Age: req.Age,
// EducationLevel: req.EducationLevel,
// Country: req.Country,
// Region: req.Region,
// PreferredLanguage: req.PreferredLanguage,
NickName: req.NickName,
Occupation: req.Occupation,
LearningGoal: req.LearningGoal,
LanguageGoal: req.LanguageGoal,
LanguageChallange: req.LanguageChallange,
FavoutiteTopic: req.FavoutiteTopic,
// NickName: req.NickName,
// Occupation: req.Occupation,
// LearningGoal: req.LearningGoal,
// LanguageGoal: req.LanguageGoal,
// LanguageChallange: req.LanguageChallange,
// FavoutiteTopic: req.FavoutiteTopic,
}
medium, err := getMedium(req.Email, req.PhoneNumber)
@ -810,18 +862,18 @@ func MapRegisterReqToUser(req domain.RegisterUserReq) domain.User {
PhoneNumber: req.PhoneNumber,
Password: []byte(req.Password), // or hashed password
Role: domain.Role(req.Role),
Age: req.Age,
EducationLevel: req.EducationLevel,
Country: req.Country,
Region: req.Region,
PreferredLanguage: req.PreferredLanguage,
// Age: req.Age,
// EducationLevel: req.EducationLevel,
// Country: req.Country,
// Region: req.Region,
// PreferredLanguage: req.PreferredLanguage,
NickName: req.NickName,
Occupation: req.Occupation,
LearningGoal: req.LearningGoal,
LanguageGoal: req.LanguageGoal,
LanguageChallange: req.LanguageChallange,
FavoutiteTopic: req.FavoutiteTopic,
// NickName: req.NickName,
// Occupation: req.Occupation,
// LearningGoal: req.LearningGoal,
// LanguageGoal: req.LanguageGoal,
// LanguageChallange: req.LanguageChallange,
// FavoutiteTopic: req.FavoutiteTopic,
}
}

View File

@ -194,6 +194,7 @@ func (a *App) initAppRoutes() {
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
// User Routes
groupV1.Get("/user/:user_id/is-profile-completed", a.authMiddleware, h.CheckProfileCompleted)
groupV1.Get("/users", a.authMiddleware, h.GetAllUsers)
groupV1.Put("/user", a.authMiddleware, h.UpdateUser)
groupV1.Put("/user/knowledge-level", h.UpdateUserKnowledgeLevel)