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" ) // type Store struct { // db *pgxpool.Pool // queries *dbgen.Queries // } // func NewStore(db *pgxpool.Pool) *Store { // return &Store{ // db: db, // queries: dbgen.New(db), // } // } func NewUserStore(s *Store) ports.UserStore { return s } 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, NickName: pgtype.Text{String: user.NickName}, 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, Suspended: user.Suspended, SuspendedAt: pgtype.Timestamptz{Time: user.SuspendedAt, Valid: !user.SuspendedAt.IsZero()}, OrganizationID: pgtype.Int8{Int64: user.OrganizationID.Value, Valid: user.OrganizationID.Valid}, CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}, UpdatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}, }) if err != nil { return domain.User{}, err } return domain.User{ ID: userRes.ID, FirstName: userRes.FirstName, LastName: userRes.LastName, NickName: userRes.NickName.String, Email: userRes.Email.String, PhoneNumber: userRes.PhoneNumber.String, Role: domain.Role(userRes.Role), Age: int(userRes.Age.Int32), EducationLevel: userRes.EducationLevel.String, Country: userRes.Country.String, Region: userRes.Region.String, EmailVerified: userRes.EmailVerified, PhoneVerified: userRes.PhoneVerified, Suspended: userRes.Suspended, SuspendedAt: userRes.SuspendedAt.Time, OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid}, CreatedAt: userRes.CreatedAt.Time, UpdatedAt: userRes.UpdatedAt.Time, }, 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 { err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{ ID: usedOtpId, UsedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}, }) if err != nil { return domain.User{}, err } } userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ FirstName: user.FirstName, LastName: user.LastName, NickName: pgtype.Text{String: user.NickName}, 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, Suspended: user.Suspended, SuspendedAt: pgtype.Timestamptz{Time: user.SuspendedAt, Valid: !user.SuspendedAt.IsZero()}, OrganizationID: pgtype.Int8{Int64: user.OrganizationID.Value, Valid: user.OrganizationID.Valid}, CreatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}, UpdatedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}, }) if err != nil { return domain.User{}, err } return domain.User{ ID: userRes.ID, FirstName: userRes.FirstName, LastName: userRes.LastName, NickName: userRes.NickName.String, Email: userRes.Email.String, PhoneNumber: userRes.PhoneNumber.String, Role: domain.Role(userRes.Role), Age: int(userRes.Age.Int32), EducationLevel: userRes.EducationLevel.String, Country: userRes.Country.String, Region: userRes.Region.String, EmailVerified: userRes.EmailVerified, PhoneVerified: userRes.PhoneVerified, Suspended: userRes.Suspended, SuspendedAt: userRes.SuspendedAt.Time, OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid}, CreatedAt: userRes.CreatedAt.Time, UpdatedAt: userRes.UpdatedAt.Time, }, nil } // GetUserByID retrieves a user by ID func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) { userRes, err := s.queries.GetUserByID(ctx, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.User{}, domain.ErrUserNotFound } return domain.User{}, err } return domain.User{ ID: userRes.ID, FirstName: userRes.FirstName, LastName: userRes.LastName, NickName: userRes.NickName.String, Email: userRes.Email.String, PhoneNumber: userRes.PhoneNumber.String, Role: domain.Role(userRes.Role), Age: int(userRes.Age.Int32), EducationLevel: userRes.EducationLevel.String, Country: userRes.Country.String, Region: userRes.Region.String, EmailVerified: userRes.EmailVerified, PhoneVerified: userRes.PhoneVerified, Suspended: userRes.Suspended, SuspendedAt: userRes.SuspendedAt.Time, OrganizationID: domain.ValidInt64{Value: userRes.OrganizationID.Int64, Valid: userRes.OrganizationID.Valid}, CreatedAt: userRes.CreatedAt.Time, UpdatedAt: userRes.UpdatedAt.Time, }, nil } // GetAllUsers retrieves users with optional filters func (s *Store) GetAllUsers( ctx context.Context, role *string, organizationID *int64, 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 { users = append(users, domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, NickName: u.NickName.String, 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, Suspended: u.Suspended, SuspendedAt: u.SuspendedAt.Time, OrganizationID: domain.ValidInt64{ Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid, }, CreatedAt: u.CreatedAt.Time, UpdatedAt: u.UpdatedAt.Time, }) } return users, totalCount, nil } // GetTotalUsers counts users with optional filters func (s *Store) GetTotalUsers(ctx context.Context, role *string, organizationID *int64) (int64, error) { count, err := s.queries.GetTotalUsers(ctx, dbgen.GetTotalUsersParams{ Role: *role, OrganizationID: pgtype.Int8{Int64: *organizationID}, }) 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, organizationID *int64, role *string) ([]domain.User, error) { rows, err := s.queries.SearchUserByNameOrPhone(ctx, dbgen.SearchUserByNameOrPhoneParams{ Column1: pgtype.Text{String: search}, OrganizationID: pgtype.Int8{Int64: *organizationID}, Role: pgtype.Text{String: *role}, }) if err != nil { return nil, err } users := make([]domain.User, len(rows)) for i, u := range rows { users[i] = domain.User{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, NickName: u.NickName.String, 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, Suspended: u.Suspended, SuspendedAt: u.SuspendedAt.Time, OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid}, CreatedAt: u.CreatedAt.Time, UpdatedAt: u.UpdatedAt.Time, } } 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{ FirstName: user.FirstName, LastName: user.LastName, Suspended: user.Suspended, ID: user.ID, }) } // 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, organizationID domain.ValidInt64) (phoneExists, emailExists bool, err error) { res, err := s.queries.CheckPhoneEmailExist(ctx, dbgen.CheckPhoneEmailExistParams{ PhoneNumber: pgtype.Text{String: phone}, Email: pgtype.Text{String: email}, OrganizationID: pgtype.Int8{Int64: organizationID.Value}, }) if err != nil { return false, false, err } return res.PhoneExists, res.EmailExists, nil } // GetUserByEmail retrieves a user by email and organization func (s *Store) GetUserByEmailPhone( ctx context.Context, email string, phone string, organizationID domain.ValidInt64, ) (domain.User, error) { user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{ Email: pgtype.Text{ String: email, Valid: email != "", }, PhoneNumber: pgtype.Text{ String: phone, Valid: phone != "", }, OrganizationID: organizationID.ToPG(), }) if err != nil { if errors.Is(err, sql.ErrNoRows) { return domain.User{}, authentication.ErrUserNotFound } return domain.User{}, err } return domain.User{ ID: user.ID, FirstName: user.FirstName, LastName: user.LastName, NickName: user.NickName.String, Email: user.Email.String, PhoneNumber: user.PhoneNumber.String, Password: user.Password, Role: domain.Role(user.Role), Age: int(user.Age.Int32), EducationLevel: user.EducationLevel.String, Country: user.Country.String, Region: user.Region.String, EmailVerified: user.EmailVerified, PhoneVerified: user.PhoneVerified, Suspended: user.Suspended, SuspendedAt: user.SuspendedAt.Time, OrganizationID: domain.ValidInt64{ Value: user.OrganizationID.Int64, Valid: user.OrganizationID.Valid, }, CreatedAt: user.CreatedAt.Time, UpdatedAt: user.UpdatedAt.Time, }, nil } // UpdatePassword updates a user's password func (s *Store) UpdatePassword(ctx context.Context, password, email, phone string, organizationID int64, updatedAt time.Time) error { return s.queries.UpdatePassword(ctx, dbgen.UpdatePasswordParams{ Password: []byte(password), Email: pgtype.Text{String: email}, PhoneNumber: pgtype.Text{String: phone}, UpdatedAt: pgtype.Timestamptz{Time: updatedAt}, 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, NickName: u.NickName.String, 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, Suspended: u.Suspended, SuspendedAt: u.SuspendedAt.Time, OrganizationID: domain.ValidInt64{Value: u.OrganizationID.Int64, Valid: u.OrganizationID.Valid}, CreatedAt: u.CreatedAt.Time, UpdatedAt: u.UpdatedAt.Time, } }