package repository import ( "bssapp-backend/models" "database/sql" "errors" "strings" "time" "github.com/lib/pq" ) var ErrMkUserNotFound = errors.New("mk_user not found") type MkUserRepository struct { DB *sql.DB } func NewMkUserRepository(db *sql.DB) *MkUserRepository { return &MkUserRepository{DB: db} } // ------------------------------------------------------- // 🔍 GET BY USERNAME // ------------------------------------------------------- func (r *MkUserRepository) GetByUsername(username string) (*models.MkUser, error) { username = strings.TrimSpace(username) var u models.MkUser err := r.DB.QueryRow(`SELECT u.id, u.username, COALESCE(u.email,'') AS email, u.is_active, COALESCE(u.password_hash,'') AS password_hash, u.force_password_change, COALESCE(r.id, 0) AS role_id, COALESCE(r.code, '') AS role_code, -- ✅ DEPARTMENTS COALESCE( array_agg(DISTINCT d.code) FILTER (WHERE d.code IS NOT NULL), '{}' ) AS department_codes, u.password_updated_at, u.created_at, u.updated_at, u.last_login_at FROM mk_dfusr u LEFT JOIN dfrole_usr ru ON ru.dfusr_id = u.id LEFT JOIN dfrole r ON r.id = ru.dfrole_id -- ✅ USER → DEPT LEFT JOIN dfusr_dprt ud ON ud.dfusr_id = u.id AND ud.is_active = true LEFT JOIN mk_dprt d ON d.id = ud.dprt_id WHERE LOWER(u.username) = LOWER($1) GROUP BY u.id, r.id LIMIT 1 `, username).Scan( &u.ID, &u.Username, &u.Email, &u.IsActive, &u.PasswordHash, &u.ForcePasswordChange, &u.RoleID, &u.RoleCode, pq.Array(&u.DepartmentCodes), // ✅ &u.PasswordUpdatedAt, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrMkUserNotFound } return nil, err } return &u, nil } // ------------------------------------------------------- // 🔍 GET BY ID // ------------------------------------------------------- func (r *MkUserRepository) GetByID(id int64) (*models.MkUser, error) { var u models.MkUser err := r.DB.QueryRow(` SELECT u.id, u.username, COALESCE(u.email,'') AS email, u.is_active, COALESCE(u.password_hash,'') AS password_hash, u.force_password_change, COALESCE(r.id, 0) AS role_id, COALESCE(r.code, '') AS role_code, -- ✅ DEPARTMENTS COALESCE( array_agg(DISTINCT d.code) FILTER (WHERE d.code IS NOT NULL), '{}' ) AS department_codes, u.password_updated_at, u.created_at, u.updated_at, u.last_login_at FROM mk_dfusr u LEFT JOIN dfrole_usr ru ON ru.dfusr_id = u.id LEFT JOIN dfrole r ON r.id = ru.dfrole_id -- ✅ USER → DEPT LEFT JOIN dfusr_dprt ud ON ud.dfusr_id = u.id AND ud.is_active = true LEFT JOIN mk_dprt d ON d.id = ud.dprt_id WHERE LOWER(u.username) = LOWER($1) GROUP BY u.id, r.id LIMIT 1 `, id).Scan( &u.ID, &u.Username, &u.Email, &u.IsActive, &u.PasswordHash, &u.ForcePasswordChange, &u.RoleID, &u.RoleCode, pq.Array(&u.DepartmentCodes), // ✅ &u.PasswordUpdatedAt, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrMkUserNotFound } return nil, err } return &u, nil } // ------------------------------------------------------- // 🔁 CREATE FROM LEGACY (dfusr → mk_dfusr) // - id = dfusr.id ✅ // - role / v3 YOK // ------------------------------------------------------- // ------------------------------------------------------- // 🔁 CREATE FROM LEGACY (dfusr → mk_dfusr) FULL MIGRATION // ------------------------------------------------------- func (r *MkUserRepository) CreateFromLegacy( legacy *models.User, passwordHash string, ) (*models.MkUser, error) { var u models.MkUser err := r.DB.QueryRow(` INSERT INTO mk_dfusr ( id, username, email, full_name, mobile, address, is_active, password_hash, force_password_change, created_at, updated_at ) VALUES ( $1,$2,$3,$4,$5,$6,$7,$8,true,now(),now() ) ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, full_name = EXCLUDED.full_name, mobile = EXCLUDED.mobile, address = EXCLUDED.address, password_hash = EXCLUDED.password_hash, force_password_change= true, updated_at = now() RETURNING id, username, COALESCE(email,''), is_active, COALESCE(password_hash,''), force_password_change, password_updated_at, created_at, updated_at, last_login_at `, legacy.ID, legacy.Username, legacy.Email, legacy.FullName, legacy.Mobile, legacy.Address, legacy.IsActive, passwordHash, ).Scan( &u.ID, &u.Username, &u.Email, &u.IsActive, &u.PasswordHash, &u.ForcePasswordChange, &u.PasswordUpdatedAt, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt, ) if err != nil { return nil, err } return &u, nil } // ------------------------------------------------------- // ➕ CREATE NEW USER (NON-LEGACY) // - id = sequence (>=1000) // ------------------------------------------------------- func (r *MkUserRepository) CreateNewUser( username string, email string, isActive bool, ) (*models.MkUser, error) { var u models.MkUser err := r.DB.QueryRow(` INSERT INTO mk_dfusr ( username, email, is_active, force_password_change, created_at, updated_at ) VALUES ( $1,$2,$3,true,now(),now() ) RETURNING id, username, COALESCE(email,'') AS email, is_active, COALESCE(password_hash,'') AS password_hash, force_password_change, password_updated_at, created_at, updated_at, last_login_at `, strings.TrimSpace(username), strings.TrimSpace(email), isActive, ).Scan( &u.ID, &u.Username, &u.Email, &u.IsActive, &u.PasswordHash, &u.ForcePasswordChange, &u.PasswordUpdatedAt, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt, ) if err != nil { return nil, err } return &u, nil } // ------------------------------------------------------- // 🕒 TOUCH LAST LOGIN // ------------------------------------------------------- func (r *MkUserRepository) TouchLastLogin(userID int64) error { _, err := r.DB.Exec(` UPDATE mk_dfusr SET last_login_at = $1, updated_at = $1 WHERE id = $2 `, time.Now(), userID) return err } // ------------------------------------------------------- // 🔐 UPDATE PASSWORD // ------------------------------------------------------- func (r *MkUserRepository) UpdatePassword(userID int64, newHash string) error { _, err := r.DB.Exec(` UPDATE mk_dfusr SET password_hash = $1, force_password_change = false, password_updated_at = NOW(), updated_at = NOW() WHERE id = $2 `, newHash, userID) return err }