- Added endpoints for listing and importing migration rosters. - Introduced audit export functionality for migration jobs in CSV and NDJSON formats. - Implemented tenant mismatch validation for Microsoft migration claims. - Enhanced error handling for email claiming and migration processes. - Added integration tests for roster import and claim workflows.
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package migration
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// HasPendingMigrationInvite reports whether an unclaimed invite exists for the email.
|
|
func HasPendingMigrationInvite(ctx context.Context, db *pgxpool.Pool, email string) (bool, error) {
|
|
if db == nil {
|
|
return false, nil
|
|
}
|
|
email = normalizeInviteEmail(email)
|
|
if email == "" {
|
|
return false, nil
|
|
}
|
|
var exists bool
|
|
err := db.QueryRow(ctx, `
|
|
SELECT EXISTS(
|
|
SELECT 1 FROM migration_invites
|
|
WHERE status = 'invited' AND lower(email) = lower($1)
|
|
)
|
|
`, email).Scan(&exists)
|
|
return exists, err
|
|
}
|
|
|
|
// ProvisionAudit counts user-linked provision artifacts for test verification.
|
|
type ProvisionAudit struct {
|
|
Users int
|
|
Mailboxes int
|
|
MailAccounts int
|
|
NCPrincipals int
|
|
}
|
|
|
|
// AuditProvisionByEmail counts rows tied to an email across users, mailboxes, mail accounts, and Nextcloud credentials.
|
|
func AuditProvisionByEmail(ctx context.Context, db *pgxpool.Pool, email string) (ProvisionAudit, error) {
|
|
var audit ProvisionAudit
|
|
if db == nil {
|
|
return audit, nil
|
|
}
|
|
email = strings.ToLower(strings.TrimSpace(email))
|
|
if email == "" {
|
|
return audit, nil
|
|
}
|
|
|
|
if err := db.QueryRow(ctx, `
|
|
SELECT COUNT(*) FROM users WHERE lower(email) = $1
|
|
`, email).Scan(&audit.Users); err != nil {
|
|
return audit, err
|
|
}
|
|
if err := db.QueryRow(ctx, `
|
|
SELECT COUNT(*)
|
|
FROM mailboxes mb
|
|
JOIN mail_domains md ON md.id = mb.domain_id
|
|
WHERE lower(mb.local_part || '@' || md.name) = $1
|
|
`, email).Scan(&audit.Mailboxes); err != nil {
|
|
return audit, err
|
|
}
|
|
if err := db.QueryRow(ctx, `
|
|
SELECT COUNT(*) FROM mail_accounts WHERE lower(email) = $1
|
|
`, email).Scan(&audit.MailAccounts); err != nil {
|
|
return audit, err
|
|
}
|
|
if err := db.QueryRow(ctx, `
|
|
SELECT COUNT(*) FROM nextcloud_dav_credentials WHERE nc_user_id = $1
|
|
`, email).Scan(&audit.NCPrincipals); err != nil {
|
|
return audit, err
|
|
}
|
|
return audit, nil
|
|
}
|
|
|
|
// LinkHostedMailboxByEmail attaches orphan mailboxes/mail_accounts (e.g. from claim-before-enroll) to a user.
|
|
func LinkHostedMailboxByEmail(ctx context.Context, db *pgxpool.Pool, userID, email string) error {
|
|
email = strings.ToLower(strings.TrimSpace(email))
|
|
if email == "" {
|
|
return nil
|
|
}
|
|
_, err := db.Exec(ctx, `
|
|
UPDATE mailboxes SET user_id = $1::uuid, updated_at = NOW()
|
|
WHERE user_id IS NULL AND lower(local_part || '@' || (SELECT name FROM mail_domains d WHERE d.id = mailboxes.domain_id)) = $2
|
|
`, userID, email)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(ctx, `
|
|
UPDATE mail_accounts ma SET user_id = $1::uuid, updated_at = NOW()
|
|
FROM mailboxes mb
|
|
JOIN mail_domains md ON md.id = mb.domain_id
|
|
WHERE mb.mail_account_id = ma.id
|
|
AND ma.user_id IS NULL
|
|
AND mb.user_id = $1::uuid
|
|
AND lower(mb.local_part || '@' || md.name) = $2
|
|
`, userID, email)
|
|
return err
|
|
}
|