332 lines
8.7 KiB
Go
332 lines
8.7 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
"tm/pkg/logger"
|
|
orm "tm/pkg/mongo"
|
|
"tm/pkg/response"
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
mongopkg "go.mongodb.org/mongo-driver/v2/mongo"
|
|
)
|
|
|
|
// Repository defines methods for user data access
|
|
type Repository interface {
|
|
Login(ctx context.Context, id string) error
|
|
Create(ctx context.Context, user *User) error
|
|
Update(ctx context.Context, user *User) error
|
|
Delete(ctx context.Context, id string) error
|
|
GetByID(ctx context.Context, id string) (*User, error)
|
|
GetByEmail(ctx context.Context, email string) (*User, error)
|
|
GetByUsername(ctx context.Context, username string) (*User, error)
|
|
GetByIDs(ctx context.Context, userIDs []string) ([]User, error)
|
|
Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) ([]User, int64, error)
|
|
}
|
|
|
|
// userRepository implements the Repository interface using the MongoDB ORM
|
|
type userRepository struct {
|
|
ormRepo orm.Repository[User]
|
|
collection *mongopkg.Collection
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewUserRepository creates a new user repository
|
|
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
|
// Create indexes using the ORM's index management
|
|
indexes := []orm.Index{
|
|
*orm.CreateTextIndex("search_idx", "full_name", "email", "username"),
|
|
}
|
|
|
|
// Create indexes
|
|
err := mongoManager.CreateIndexes("users", indexes)
|
|
if err != nil {
|
|
logger.Warn("Failed to create user indexes", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
// Create ORM repository
|
|
collection := mongoManager.GetCollection("users")
|
|
ormRepo := orm.NewRepository[User](collection, logger)
|
|
|
|
return &userRepository{
|
|
ormRepo: ormRepo,
|
|
collection: collection,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Login updates the last login timestamp
|
|
func (r *userRepository) Login(ctx context.Context, id string) error {
|
|
// Get user first
|
|
user, err := r.GetByID(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update last login timestamp
|
|
user.LastLoginAt = &[]int64{time.Now().Unix()}[0]
|
|
user.SetUpdatedAt(time.Now().Unix())
|
|
|
|
// Update in database
|
|
err = r.ormRepo.Update(ctx, user)
|
|
if err != nil {
|
|
r.logger.Error("Failed to update last login", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"user_id": id,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Last login updated successfully", map[string]interface{}{
|
|
"user_id": id,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// Create creates a new user
|
|
func (r *userRepository) Create(ctx context.Context, user *User) error {
|
|
// Set created timestamp using Unix timestamp
|
|
now := time.Now().Unix()
|
|
user.SetCreatedAt(now)
|
|
|
|
// Use ORM to create user
|
|
err := r.ormRepo.Create(ctx, user)
|
|
if err != nil {
|
|
r.logger.Error("Failed to create user", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"email": user.Email,
|
|
"user_id": user.ID,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("User created successfully", map[string]interface{}{
|
|
"user_id": user.ID,
|
|
"email": user.Email,
|
|
"role": user.Role,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update updates a user
|
|
func (r *userRepository) Update(ctx context.Context, user *User) error {
|
|
// Set updated timestamp using Unix timestamp
|
|
user.SetUpdatedAt(time.Now().Unix())
|
|
|
|
// Use ORM to update user
|
|
err := r.ormRepo.Update(ctx, user)
|
|
if err != nil {
|
|
// Backward compatibility for legacy records with string _id.
|
|
legacyFilterID := user.ID.Hex()
|
|
updateDoc := bson.M{
|
|
"$set": bson.M{
|
|
"full_name": user.FullName,
|
|
"username": user.Username,
|
|
"email": user.Email,
|
|
"password": user.Password,
|
|
"role": user.Role,
|
|
"status": user.Status,
|
|
"department": user.Department,
|
|
"position": user.Position,
|
|
"phone": user.Phone,
|
|
"profile_image": user.ProfileImage,
|
|
"device_token": user.DeviceToken,
|
|
"is_verified": user.IsVerified,
|
|
"last_login_at": user.LastLoginAt,
|
|
"updated_at": user.UpdatedAt,
|
|
},
|
|
}
|
|
legacyResult, legacyErr := r.collection.UpdateOne(ctx, bson.M{"_id": legacyFilterID}, updateDoc)
|
|
if legacyErr == nil && legacyResult.MatchedCount > 0 {
|
|
r.logger.Info("User updated successfully using legacy string ID", map[string]interface{}{
|
|
"user_id": legacyFilterID,
|
|
"email": user.Email,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
r.logger.Error("Failed to update user", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"user_id": user.ID,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("User updated successfully", map[string]interface{}{
|
|
"user_id": user.ID,
|
|
"email": user.Email,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetByID retrieves a user by ID
|
|
func (r *userRepository) GetByID(ctx context.Context, id string) (*User, error) {
|
|
user, err := r.ormRepo.FindByID(ctx, id)
|
|
if err != nil {
|
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
|
// Backward compatibility: some legacy seed data may store _id as string instead of ObjectID.
|
|
legacyUser, legacyErr := r.ormRepo.FindOne(ctx, bson.M{"_id": id})
|
|
if legacyErr == nil {
|
|
return legacyUser, nil
|
|
}
|
|
if errors.Is(legacyErr, orm.ErrDocumentNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
r.logger.Error("Failed to get user by legacy string ID", map[string]interface{}{
|
|
"error": legacyErr.Error(),
|
|
"user_id": id,
|
|
})
|
|
return nil, legacyErr
|
|
}
|
|
r.logger.Error("Failed to get user by ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"user_id": id,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// GetByEmail retrieves a user by email
|
|
func (r *userRepository) GetByEmail(ctx context.Context, email string) (*User, error) {
|
|
user, err := r.ormRepo.FindOne(ctx, bson.M{"email": email})
|
|
if err != nil {
|
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
r.logger.Error("Failed to get user by email", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"email": email,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// GetByUsername retrieves a user by username
|
|
func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) {
|
|
user, err := r.ormRepo.FindOne(ctx, bson.M{"username": username})
|
|
if err != nil {
|
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
r.logger.Error("Failed to get user by username", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"username": username,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// Delete deletes a user (soft delete by setting status to inactive)
|
|
func (r *userRepository) Delete(ctx context.Context, id string) error {
|
|
// Get user first
|
|
_, err := r.GetByID(ctx, id)
|
|
if err != nil {
|
|
r.logger.Error("Failed to delete user", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"user_id": id,
|
|
})
|
|
if errors.Is(err, orm.ErrDocumentNotFound) {
|
|
return errors.New("user not found")
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Delete in database
|
|
err = r.ormRepo.Delete(ctx, id)
|
|
if err != nil {
|
|
r.logger.Error("Failed to delete user", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"user_id": id,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("User deleted successfully", map[string]interface{}{
|
|
"user_id": id,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetByIDs retrieves users by IDs
|
|
func (r *userRepository) GetByIDs(ctx context.Context, userIDs []string) ([]User, error) {
|
|
objectIDs := make([]bson.ObjectID, 0)
|
|
for _, id := range userIDs {
|
|
objectID, err := bson.ObjectIDFromHex(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
objectIDs = append(objectIDs, objectID)
|
|
}
|
|
|
|
users, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": objectIDs}}, orm.NewPaginationBuilder().Limit(len(userIDs)).Build())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return users.Items, nil
|
|
}
|
|
|
|
// List retrieves users with pagination
|
|
func (r *userRepository) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) ([]User, int64, error) {
|
|
filter := bson.M{}
|
|
|
|
if form.Search != nil {
|
|
filter["$or"] = bson.A{
|
|
bson.M{"full_name": bson.M{"$regex": form.Search, "$options": "i"}},
|
|
bson.M{"email": bson.M{"$regex": form.Search, "$options": "i"}},
|
|
bson.M{"username": bson.M{"$regex": form.Search, "$options": "i"}},
|
|
}
|
|
}
|
|
|
|
if form.Status != nil {
|
|
filter["status"] = form.Status
|
|
}
|
|
|
|
if form.Role != nil {
|
|
filter["role"] = form.Role
|
|
}
|
|
|
|
// Build sort
|
|
sort := "created_at"
|
|
if pagination.SortBy != "" {
|
|
sort = pagination.SortBy
|
|
}
|
|
|
|
sortOrder := "desc"
|
|
if pagination.SortOrder != "" {
|
|
sortOrder = pagination.SortOrder
|
|
}
|
|
|
|
// Build pagination
|
|
paginationBuilder := orm.NewPaginationBuilder().
|
|
Limit(pagination.Limit).
|
|
Skip(pagination.Offset).
|
|
SortBy(sort, sortOrder).
|
|
Build()
|
|
|
|
// Use ORM to find users
|
|
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
|
|
if err != nil {
|
|
r.logger.Error("Failed to list users", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return nil, 0, err
|
|
}
|
|
|
|
return result.Items, result.TotalCount, nil
|
|
}
|