package repository import ( dbgen "Yimaru-Backend/gen/db" "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/ports" "Yimaru-Backend/internal/services/authentication" "context" "database/sql" "errors" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) func NewUserStore(s *Store) ports.UserStore { return s } func (s *Store) IsUserPending(ctx context.Context, UserName string) (bool, error) { isPending, err := s.queries.IsUserPending(ctx, UserName) if err != nil { if errors.Is(err, sql.ErrNoRows) { return false, authentication.ErrUserNotFound } return false, err } return isPending, nil } func (s *Store) IsUserNameUnique(ctx context.Context, userName string) (bool, error) { isUnique, err := s.queries.IsUserNameUnique(ctx, userName) if err != nil { return false, err } return isUnique, nil } func (s *Store) CreateUserWithoutOtp( ctx context.Context, user domain.User, ) (domain.User, error) { userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ FirstName: user.FirstName, LastName: user.LastName, UserName: user.UserName, Email: pgtype.Text{String: user.Email, Valid: user.Email != ""}, PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""}, Role: string(user.Role), Password: user.Password, Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0}, EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""}, Country: pgtype.Text{String: user.Country, Valid: user.Country != ""}, Region: pgtype.Text{String: user.Region, Valid: user.Region != ""}, EmailVerified: user.EmailVerified, PhoneVerified: user.PhoneVerified, Status: string(user.Status), ProfileCompleted: user.ProfileCompleted, PreferredLanguage: pgtype.Text{ String: user.PreferredLanguage, Valid: user.PreferredLanguage != "", }, // OrganizationID: user.OrganizationID.ToPG(), }) if err != nil { return domain.User{}, err } var updatedAt *time.Time if userRes.UpdatedAt.Valid { updatedAt = &userRes.UpdatedAt.Time } return domain.User{ ID: userRes.ID, FirstName: userRes.FirstName, LastName: userRes.LastName, UserName: userRes.UserName, Email: userRes.Email.String, PhoneNumber: userRes.PhoneNumber.String, Role: domain.Role(userRes.Role), Password: user.Password, Age: int(userRes.Age.Int32), EducationLevel: userRes.EducationLevel.String, Country: userRes.Country.String, Region: userRes.Region.String, EmailVerified: userRes.EmailVerified, PhoneVerified: userRes.PhoneVerified, Status: domain.UserStatus(userRes.Status), ProfileCompleted: userRes.ProfileCompleted, PreferredLanguage: userRes.PreferredLanguage.String, CreatedAt: userRes.CreatedAt.Time, UpdatedAt: updatedAt, }, nil } // CreateUser inserts a new user into the database func (s *Store) CreateUser( ctx context.Context, user domain.User, usedOtpId int64, ) (domain.User, error) { // Optional: mark OTP as used if usedOtpId > 0 { if err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{ ID: usedOtpId, UsedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}, }); err != nil { return domain.User{}, err } } userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ FirstName: user.FirstName, LastName: user.LastName, UserName: user.UserName, Email: pgtype.Text{String: user.Email, Valid: user.Email != ""}, PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""}, Role: string(user.Role), Password: user.Password, Age: pgtype.Int4{Int32: int32(user.Age), Valid: user.Age > 0}, EducationLevel: pgtype.Text{String: user.EducationLevel, Valid: user.EducationLevel != ""}, Country: pgtype.Text{String: user.Country, Valid: user.Country != ""}, Region: pgtype.Text{String: user.Region, Valid: user.Region != ""}, EmailVerified: user.EmailVerified, PhoneVerified: user.PhoneVerified, Status: string(user.Status), ProfileCompleted: user.ProfileCompleted, PreferredLanguage: pgtype.Text{ String: user.PreferredLanguage, Valid: user.PreferredLanguage != "", }, // OrganizationID: user.OrganizationID.ToPG(), }) if err != nil { return domain.User{}, err } var updatedAt *time.Time if userRes.UpdatedAt.Valid { updatedAt = &userRes.UpdatedAt.Time } return domain.User{ ID: userRes.ID, FirstName: userRes.FirstName, LastName: userRes.LastName, UserName: userRes.UserName, Email: userRes.Email.String, PhoneNumber: userRes.PhoneNumber.String, Role: domain.Role(userRes.Role), Password: user.Password, Age: int(userRes.Age.Int32), EducationLevel: userRes.EducationLevel.String, Country: userRes.Country.String, Region: userRes.Region.String, EmailVerified: userRes.EmailVerified, PhoneVerified: userRes.PhoneVerified, Status: domain.UserStatus(userRes.Status), ProfileCompleted: userRes.ProfileCompleted, PreferredLanguage: userRes.PreferredLanguage.String, CreatedAt: userRes.CreatedAt.Time, UpdatedAt: updatedAt, }, nil } // GetUserByID retrieves a user by ID func (s *Store) GetUserByID( ctx context.Context, id int64, ) (domain.User, error) { u, err := s.queries.GetUserByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.User{}, domain.ErrUserNotFound } return domain.User{}, err } var lastLogin *time.Time if u.LastLogin.Valid { lastLogin = &u.LastLogin.Time } var updatedAt *time.Time if u.UpdatedAt.Valid { updatedAt = &u.UpdatedAt.Time } return domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Role: domain.Role(u.Role), Age: int(u.Age.Int32), EducationLevel: u.EducationLevel.String, Country: u.Country.String, Region: u.Region.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), LastLogin: lastLogin, ProfileCompleted: u.ProfileCompleted, ProfilePictureURL: u.ProfilePictureUrl.String, PreferredLanguage: u.PreferredLanguage.String, // OrganizationID: domain.ValidInt64{ // Value: u.OrganizationID.Int64, // Valid: u.OrganizationID.Valid, // }, CreatedAt: u.CreatedAt.Time, UpdatedAt: updatedAt, }, nil } // GetAllUsers retrieves users with optional filters func (s *Store) GetAllUsers( ctx context.Context, role *string, query *string, createdBefore, createdAfter *time.Time, limit, offset int32, ) ([]domain.User, int64, error) { params := dbgen.GetAllUsersParams{ Limit: pgtype.Int4{Int32: limit, Valid: true}, Offset: pgtype.Int4{Int32: offset, Valid: true}, } if role != nil { params.Role = *role } // if organizationID != nil { // params.OrganizationID = pgtype.Int8{Int64: *organizationID, Valid: true} // } if query != nil { params.Query = pgtype.Text{String: *query, Valid: true} } if createdBefore != nil { params.CreatedBefore = pgtype.Timestamptz{Time: *createdBefore, Valid: true} } if createdAfter != nil { params.CreatedAfter = pgtype.Timestamptz{Time: *createdAfter, Valid: true} } rows, err := s.queries.GetAllUsers(ctx, params) if err != nil { return nil, 0, err } if len(rows) == 0 { return []domain.User{}, 0, nil } totalCount := rows[0].TotalCount users := make([]domain.User, 0, len(rows)) for _, u := range rows { var updatedAt *time.Time if u.UpdatedAt.Valid { updatedAt = &u.UpdatedAt.Time } users = append(users, domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Role: domain.Role(u.Role), Age: int(u.Age.Int32), EducationLevel: u.EducationLevel.String, Country: u.Country.String, Region: u.Region.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), ProfileCompleted: u.ProfileCompleted, PreferredLanguage: u.PreferredLanguage.String, // OrganizationID: domain.ValidInt64{ // Value: u.OrganizationID.Int64, // Valid: u.OrganizationID.Valid, // }, CreatedAt: u.CreatedAt.Time, UpdatedAt: updatedAt, }) } return users, totalCount, nil } // GetTotalUsers counts users with optional filters func (s *Store) GetTotalUsers(ctx context.Context, role *string) (int64, error) { count, err := s.queries.GetTotalUsers(ctx, *role) if err != nil { return 0, err } return count, nil } // SearchUserByNameOrPhone searches users by name or phone func (s *Store) SearchUserByNameOrPhone( ctx context.Context, search string, role *string, ) ([]domain.User, error) { params := dbgen.SearchUserByNameOrPhoneParams{ Column1: pgtype.Text{ String: search, Valid: search != "", }, } // if organizationID != nil { // params.OrganizationID = pgtype.Int8{ // Int64: *organizationID, // Valid: true, // } // } if role != nil { params.Role = pgtype.Text{ String: *role, Valid: true, } } rows, err := s.queries.SearchUserByNameOrPhone(ctx, params) if err != nil { return nil, err } users := make([]domain.User, 0, len(rows)) for _, u := range rows { var updatedAt *time.Time if u.UpdatedAt.Valid { updatedAt = &u.UpdatedAt.Time } users = append(users, domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Role: domain.Role(u.Role), Age: int(u.Age.Int32), EducationLevel: u.EducationLevel.String, Country: u.Country.String, Region: u.Region.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), ProfileCompleted: u.ProfileCompleted, // OrganizationID: domain.ValidInt64{ // Value: u.OrganizationID.Int64, // Valid: u.OrganizationID.Valid, // }, CreatedAt: u.CreatedAt.Time, UpdatedAt: updatedAt, }) } return users, nil } // UpdateUser updates basic user info func (s *Store) UpdateUser(ctx context.Context, user domain.User) error { return s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{ ID: user.ID, FirstName: user.FirstName, LastName: user.LastName, Status: string(user.Status), }) } // UpdateUserOrganization updates a user's organization // func (s *Store) UpdateUserOrganization(ctx context.Context, userID, organizationID int64) error { // return s.queries.UpdateUserOrganization(ctx, dbgen.UpdateUserOrganizationParams{ // OrganizationID: pgtype.Int8{Int64: organizationID, Valid: true}, // ID: userID, // }) // } // SuspendUser suspends a user // func (s *Store) SuspendUser(ctx context.Context, userID int64, suspended bool, suspendedAt time.Time) error { // return s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{ // Suspended: suspended, // SuspendedAt: pgtype.Timestamptz{Time: suspendedAt, Valid: true}, // ID: userID, // }) // } // DeleteUser removes a user func (s *Store) DeleteUser(ctx context.Context, userID int64) error { return s.queries.DeleteUser(ctx, userID) } // CheckPhoneEmailExist checks if phone or email exists in an organization func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string) (phoneExists, emailExists bool, err error) { res, err := s.queries.CheckPhoneEmailExist(ctx, dbgen.CheckPhoneEmailExistParams{ PhoneNumber: pgtype.Text{String: phone}, Email: pgtype.Text{String: email}, }) if err != nil { return false, false, err } return res.PhoneExists, res.EmailExists, nil } func (s *Store) GetUserByUserName( ctx context.Context, userName string, ) (domain.User, error) { u, err := s.queries.GetUserByUserName(ctx, userName) if err != nil { if errors.Is(err, sql.ErrNoRows) { return domain.User{}, authentication.ErrUserNotFound } return domain.User{}, err } var lastLogin *time.Time if u.LastLogin.Valid { lastLogin = &u.LastLogin.Time } var updatedAt *time.Time if u.UpdatedAt.Valid { updatedAt = &u.UpdatedAt.Time } return domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Password: u.Password, Role: domain.Role(u.Role), Age: int(u.Age.Int32), EducationLevel: u.EducationLevel.String, Country: u.Country.String, Region: u.Region.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), LastLogin: lastLogin, ProfileCompleted: u.ProfileCompleted, PreferredLanguage: u.PreferredLanguage.String, // OrganizationID: domain.ValidInt64{ // Value: u.OrganizationID.Int64, // Valid: u.OrganizationID.Valid, // }, CreatedAt: u.CreatedAt.Time, UpdatedAt: updatedAt, }, nil } // GetUserByEmail retrieves a user by email and organization func (s *Store) GetUserByEmailPhone( ctx context.Context, email string, phone string, ) (domain.User, error) { u, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{ Email: pgtype.Text{ String: email, Valid: email != "", }, PhoneNumber: pgtype.Text{ String: phone, Valid: phone != "", }, }) if err != nil { if errors.Is(err, sql.ErrNoRows) { return domain.User{}, authentication.ErrUserNotFound } return domain.User{}, err } var lastLogin *time.Time if u.LastLogin.Valid { lastLogin = &u.LastLogin.Time } var updatedAt *time.Time if u.UpdatedAt.Valid { updatedAt = &u.UpdatedAt.Time } return domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Password: u.Password, Role: domain.Role(u.Role), Age: int(u.Age.Int32), EducationLevel: u.EducationLevel.String, Country: u.Country.String, Region: u.Region.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), LastLogin: lastLogin, ProfileCompleted: u.ProfileCompleted, PreferredLanguage: u.PreferredLanguage.String, // OrganizationID: domain.ValidInt64{ // Value: u.OrganizationID.Int64, // Valid: u.OrganizationID.Valid, // }, CreatedAt: u.CreatedAt.Time, UpdatedAt: updatedAt, }, nil } // UpdatePassword updates a user's password func (s *Store) UpdatePassword(ctx context.Context, password, email, phone string, updatedAt time.Time) error { return s.queries.UpdatePassword(ctx, dbgen.UpdatePasswordParams{ Password: []byte(password), Email: pgtype.Text{String: email}, PhoneNumber: pgtype.Text{String: phone}, // OrganizationID: pgtype.Int8{Int64: organizationID}, }) } // GetOwnerByOrganizationID retrieves the owner user of an organization // func (s *Store) GetOwnerByOrganizationID(ctx context.Context, organizationID int64) (domain.User, error) { // userRes, err := s.queries.GetOwnerByOrganizationID(ctx, organizationID) // if err != nil { // return domain.User{}, err // } // return mapUser(userRes), nil // } // func (s *Store) UpdateUserSuspend(ctx context.Context, id int64, status bool) error { // err := s.queries.SuspendUser(ctx, dbgen.SuspendUserParams{ // ID: id, // Suspended: status, // SuspendedAt: pgtype.Timestamptz{ // Time: time.Now(), // Valid: true, // }, // }) // if err != nil { // return err // } // return nil // } // mapUser converts dbgen.User to domain.User func MapUser(u dbgen.User) domain.User { return domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Role: domain.Role(u.Role), Age: int(u.Age.Int32), EducationLevel: u.EducationLevel.String, Country: u.Country.String, Region: u.Region.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), LastLogin: &u.LastLogin.Time, ProfileCompleted: u.ProfileCompleted, PreferredLanguage: u.PreferredLanguage.String, // OrganizationID: domain.ValidInt64{ // Value: u.OrganizationID.Int64, // Valid: u.OrganizationID.Valid, // }, CreatedAt: u.CreatedAt.Time, } }