package repository import ( "context" "database/sql" "errors" "time" dbgen "Yimaru-Backend/gen/db" "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/ports" "Yimaru-Backend/internal/services/authentication" "github.com/jackc/pgx/v5/pgtype" ) // NewTokenStore returns a TokenStore implementation func NewTokenStore(s *Store) ports.TokenStore { return s } // CreateRefreshToken inserts a new refresh token into the database func (s *Store) CreateRefreshToken(ctx context.Context, rt domain.RefreshToken) error { return s.queries.CreateRefreshToken(ctx, dbgen.CreateRefreshTokenParams{ UserID: rt.UserID, Token: rt.Token, ExpiresAt: pgtype.Timestamptz{Time: rt.ExpiresAt}, CreatedAt: pgtype.Timestamptz{Time: rt.CreatedAt}, Revoked: rt.Revoked, }) } // GetRefreshToken retrieves a refresh token by its token string func (s *Store) GetRefreshToken(ctx context.Context, token string) (domain.RefreshToken, error) { rf, err := s.queries.GetRefreshToken(ctx, token) if err != nil { if errors.Is(err, sql.ErrNoRows) { return domain.RefreshToken{}, authentication.ErrRefreshTokenNotFound } return domain.RefreshToken{}, err } return domain.RefreshToken{ Token: rf.Token, UserID: rf.UserID, CreatedAt: rf.CreatedAt.Time, ExpiresAt: rf.ExpiresAt.Time, Revoked: rf.Revoked, }, nil } // GetRefreshTokenByUserID retrieves a refresh token for a specific user func (s *Store) GetRefreshTokenByUserID(ctx context.Context, id int64) (domain.RefreshToken, error) { rf, err := s.queries.GetRefreshTokenByUserID(ctx, id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return domain.RefreshToken{}, authentication.ErrRefreshTokenNotFound } return domain.RefreshToken{}, err } return domain.RefreshToken{ Token: rf.Token, UserID: rf.UserID, CreatedAt: rf.CreatedAt.Time, ExpiresAt: rf.ExpiresAt.Time, Revoked: rf.Revoked, }, nil } // RevokeRefreshToken marks a refresh token as revoked func (s *Store) RevokeRefreshToken(ctx context.Context, token string) error { return s.queries.RevokeRefreshToken(ctx, token) } // GetUserByEmailOrPhone retrieves a user by email or phone number and optional organization ID func (s *Store) GetUserByEmailOrPhone( 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 != "", }, // OrganizationID: pgtype.Int8{Int64: organizationID}, }) 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, 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 }