- Added configuration options for Stalwart hosted mail in .env.example. - Updated Docker Compose to include Stalwart service with health checks. - Introduced new API endpoints for managing mail domains and migration projects. - Enhanced Authentik blueprints for user enrollment and post-migration security. - Updated OAuth handling for Google and Microsoft migration processes. - Improved error handling and response structures in the mail API. - Added integration tests for email claiming and migration workflows.
95 lines
2.2 KiB
Go
95 lines
2.2 KiB
Go
package migration
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/mail/hosted"
|
|
)
|
|
|
|
var projectSelectColumns = []string{
|
|
"id::text",
|
|
"COALESCE(domain_id::text, '')",
|
|
"name",
|
|
"source_provider",
|
|
"auth_mode",
|
|
"status",
|
|
"cutover_at::text",
|
|
"delta_mode",
|
|
"created_at::text",
|
|
"NULLIF(microsoft_tenant_id, '')",
|
|
"microsoft_admin_consent_at::text",
|
|
"COALESCE(NULLIF(microsoft_admin_consent_error, ''), '')",
|
|
"cutover_dns_json",
|
|
}
|
|
|
|
func projectSelectSQL(tablePrefix string) string {
|
|
if tablePrefix != "" && !strings.HasSuffix(tablePrefix, ".") {
|
|
tablePrefix += "."
|
|
}
|
|
cols := make([]string, len(projectSelectColumns))
|
|
for i, col := range projectSelectColumns {
|
|
cols[i] = tablePrefix + col
|
|
}
|
|
return strings.Join(cols, ", ")
|
|
}
|
|
|
|
type projectScanner struct {
|
|
project Project
|
|
cutoverDNSRaw []byte
|
|
}
|
|
|
|
func newProjectScanner() *projectScanner {
|
|
return &projectScanner{}
|
|
}
|
|
|
|
func (s *projectScanner) targets() []any {
|
|
return []any{
|
|
&s.project.ID, &s.project.DomainID, &s.project.Name, &s.project.SourceProvider,
|
|
&s.project.AuthMode, &s.project.Status, &s.project.CutoverAt, &s.project.DeltaMode,
|
|
&s.project.CreatedAt, &s.project.MicrosoftTenantID, &s.project.MicrosoftAdminConsentAt,
|
|
&s.project.MicrosoftAdminConsentError, &s.cutoverDNSRaw,
|
|
}
|
|
}
|
|
|
|
func (s *projectScanner) result() Project {
|
|
applyCutoverDNS(&s.project, s.cutoverDNSRaw)
|
|
return s.project
|
|
}
|
|
|
|
func applyCutoverDNS(p *Project, raw []byte) {
|
|
p.CutoverDNS = nil
|
|
raw = bytesTrimSpace(raw)
|
|
if len(raw) == 0 || string(raw) == "{}" || string(raw) == "null" {
|
|
return
|
|
}
|
|
var report hosted.DNSCheckReport
|
|
if err := json.Unmarshal(raw, &report); err != nil {
|
|
return
|
|
}
|
|
if !dnsReportHasContent(report) {
|
|
return
|
|
}
|
|
p.CutoverDNS = &report
|
|
}
|
|
|
|
func dnsReportHasContent(r hosted.DNSCheckReport) bool {
|
|
if strings.TrimSpace(r.Domain) != "" {
|
|
return true
|
|
}
|
|
if len(r.Errors) > 0 || len(r.Warnings) > 0 {
|
|
return true
|
|
}
|
|
if len(r.MXRecords) > 0 || len(r.TXTRecords) > 0 || len(r.ExpectedMX) > 0 {
|
|
return true
|
|
}
|
|
if r.TXTVerified || r.MXVerified {
|
|
return true
|
|
}
|
|
return strings.TrimSpace(r.TXTExpected) != ""
|
|
}
|
|
|
|
func bytesTrimSpace(b []byte) []byte {
|
|
return []byte(strings.TrimSpace(string(b)))
|
|
}
|