166 lines
5.4 KiB
Go
166 lines
5.4 KiB
Go
package service
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models"
|
|
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/repository"
|
|
"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/database"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var (
|
|
ErrEmailExists = errors.New("email already exists")
|
|
ErrUserNotCreated = errors.New("failed to create user")
|
|
ErrInvalidRole = errors.New("invalid user role")
|
|
ErrInvalidPatient = errors.New("invalid patient data")
|
|
ErrInvalidDoctor = errors.New("invalid doctor data")
|
|
ErrTransaction = errors.New("transaction error")
|
|
)
|
|
|
|
// RegisterRequest represents a user registration request
|
|
type RegisterRequest struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
Name string `json:"name"`
|
|
Role string `json:"role"` // "patient", "ref_doctor", "expertise_doctor", or "admin"
|
|
Patient *models.PatientDetails `json:"patient,omitempty"`
|
|
Doctor *models.DoctorDetails `json:"doctor,omitempty"`
|
|
Studies []models.Study `json:"studies,omitempty"` // Study records for patient
|
|
}
|
|
|
|
// RegisterService handles user registration
|
|
type RegisterService struct {
|
|
userRepo *repository.UserRepository
|
|
patientRepo *repository.PatientRepository
|
|
doctorRepo *repository.DoctorRepository
|
|
studyRepo *repository.StudyRepository
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewRegisterService creates a new register service
|
|
func NewRegisterService(logger *zap.Logger) *RegisterService {
|
|
if logger == nil {
|
|
// If no logger is provided, create a no-op logger
|
|
logger = zap.NewNop()
|
|
}
|
|
|
|
return &RegisterService{
|
|
userRepo: repository.NewUserRepository(),
|
|
patientRepo: repository.NewPatientRepository(),
|
|
doctorRepo: repository.NewDoctorRepository(),
|
|
studyRepo: repository.NewStudyRepository(),
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Register creates a new user with their associated role-specific data
|
|
func (s *RegisterService) Register(req *RegisterRequest) (*models.User, error) {
|
|
// Validate role
|
|
if req.Role != "patient" && req.Role != "ref_doctor" && req.Role != "expertise_doctor" && req.Role != "admin" {
|
|
return nil, ErrInvalidRole
|
|
}
|
|
|
|
// Check role-specific data
|
|
if req.Role == "patient" && (req.Patient == nil || req.Patient.PatientID == "" || req.Patient.DateOfBirth == "") {
|
|
return nil, ErrInvalidPatient
|
|
}
|
|
|
|
if (req.Role == "ref_doctor" || req.Role == "expertise_doctor") && (req.Doctor == nil || req.Doctor.DoctorID == "") {
|
|
return nil, ErrInvalidDoctor
|
|
}
|
|
|
|
// Check if email already exists - do this outside the transaction
|
|
existingUser, err := s.userRepo.GetUserByEmail(req.Email)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return nil, fmt.Errorf("error checking existing user: %w", err)
|
|
}
|
|
|
|
if existingUser != nil {
|
|
return nil, ErrEmailExists
|
|
}
|
|
|
|
// Start a transaction
|
|
tx, err := database.DB.Beginx()
|
|
if err != nil {
|
|
s.logger.Error("Failed to begin transaction", zap.Error(err))
|
|
return nil, fmt.Errorf("%w: failed to begin transaction", ErrTransaction)
|
|
}
|
|
|
|
// Ensure the transaction is rolled back if we return an error
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
s.logger.Error("Panic in transaction", zap.Any("recover", r))
|
|
tx.Rollback()
|
|
panic(r) // re-throw the panic after cleanup
|
|
}
|
|
}()
|
|
|
|
// Create user
|
|
newUser := &models.User{
|
|
Email: req.Email,
|
|
Password: req.Password, // Will be hashed in repository
|
|
Role: req.Role,
|
|
Name: req.Name,
|
|
}
|
|
|
|
// Use the transaction for user creation
|
|
if err := s.userRepo.CreateUserTx(tx, newUser); err != nil {
|
|
s.logger.Error("Failed to create user", zap.Error(err), zap.String("email", req.Email))
|
|
tx.Rollback()
|
|
return nil, fmt.Errorf("error creating user: %w", err)
|
|
}
|
|
|
|
// Create role-specific data
|
|
if req.Role == "patient" {
|
|
err = s.patientRepo.CreatePatientTx(tx, req.Patient, newUser.ID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to create patient", zap.Error(err), zap.String("patientID", req.Patient.PatientID))
|
|
tx.Rollback()
|
|
return nil, fmt.Errorf("error creating patient: %w", err)
|
|
}
|
|
|
|
// Create associated study records if provided
|
|
if len(req.Studies) > 0 {
|
|
for _, study := range req.Studies {
|
|
if study.StudyInstanceUID == "" {
|
|
continue // Skip studies without UIDs
|
|
}
|
|
|
|
err = s.studyRepo.CreateStudyTx(tx, req.Patient.PatientID, study)
|
|
if err != nil {
|
|
s.logger.Error("Failed to create study",
|
|
zap.Error(err),
|
|
zap.String("patientID", req.Patient.PatientID),
|
|
zap.String("studyUID", study.StudyInstanceUID))
|
|
tx.Rollback()
|
|
return nil, fmt.Errorf("error creating study for patient: %w", err)
|
|
}
|
|
}
|
|
}
|
|
} else if req.Role == "ref_doctor" || req.Role == "expertise_doctor" {
|
|
err = s.doctorRepo.CreateDoctorTx(tx, req.Doctor, newUser.ID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to create doctor", zap.Error(err), zap.String("doctorID", req.Doctor.DoctorID))
|
|
tx.Rollback()
|
|
return nil, fmt.Errorf("error creating doctor: %w", err)
|
|
}
|
|
}
|
|
|
|
// Commit the transaction
|
|
if err := tx.Commit(); err != nil {
|
|
s.logger.Error("Failed to commit transaction", zap.Error(err))
|
|
tx.Rollback() // This is actually redundant as the transaction will be rolled back on failure
|
|
return nil, fmt.Errorf("%w: failed to commit transaction", ErrTransaction)
|
|
}
|
|
|
|
s.logger.Info("Successfully registered new user",
|
|
zap.String("email", req.Email),
|
|
zap.String("role", req.Role),
|
|
zap.String("userID", newUser.ID))
|
|
|
|
return newUser, nil
|
|
}
|