Files
go-ohif-proxy/internal/api/service/register_service.go
mario c35ec4180d Squashed commit of the following:
commit d2ec8c0f07
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Thu May 15 15:42:33 2025 +0700

    add: db tx commit and rollback implementation

commit 264435f67e
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Thu May 15 14:34:20 2025 +0700

    fix: shortlink generation logic update/create

commit 047ab1937a
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Thu May 15 11:06:04 2025 +0700

    fix: if multiple studies patient, show first study by default

commit c13f834b92
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Thu May 15 09:46:32 2025 +0700

    add: register and login with DB query AND some struct type correction

commit dd4451c2a8
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Wed May 14 10:23:33 2025 +0700

    new file structure & koneksi ke DB

commit 8289881df3
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Tue May 13 16:49:07 2025 +0700

    edit: rm debug route

commit dd784da232
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Tue May 13 15:44:11 2025 +0700

    add: implement shortlink

commit 2687a761cc
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Tue May 13 11:47:19 2025 +0700

    add new dummy doctor user

commit eb67eaca46
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Tue May 13 11:46:28 2025 +0700

    add: ref_doctor studylist filter

commit 0d4825d152
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Tue May 13 10:07:16 2025 +0700

    edit study_iuids & accNum in patient jwt to array

commit 2d1f135fda
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Tue May 13 09:52:45 2025 +0700

    patient see their multiple studies

commit 13bb380f51
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Fri May 9 10:13:16 2025 +0700

    add: cors handler route and readme

commit 6c9ab574ce
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Mon May 5 11:50:36 2025 +0700

    add: login & token validation tapi belum connect ke DB

commit 297c9a6a01
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Mon Apr 28 15:37:02 2025 +0700

    add readme.md

commit 9b8e0260f3
Author: mario <dev.mario@sismedika@gmail.com>
Date:   Mon Apr 7 15:46:07 2025 +0700

    connected-to-google

commit f340bc5916
Author: mario <dev.mario@sismedika.com>
Date:   Mon Apr 7 11:14:18 2025 +0700

    init
2025-05-15 15:50:40 +07:00

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
}