- 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.
609 lines
19 KiB
Go
609 lines
19 KiB
Go
package admin
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
|
|
"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
|
|
"github.com/ultisuite/ulti-backend/internal/api/query"
|
|
migr "github.com/ultisuite/ulti-backend/internal/migration"
|
|
)
|
|
|
|
const maxAdminMailRequestBody = 1 << 20
|
|
|
|
func (h *Handler) registerMailAdminRoutes(r chi.Router, read, write func(http.Handler) http.Handler) {
|
|
if h.svc.hosted == nil && h.svc.migration == nil {
|
|
return
|
|
}
|
|
r.Route("/mail", func(r chi.Router) {
|
|
if h.svc.hosted != nil {
|
|
r.With(read).Get("/domains", h.ListMailDomains)
|
|
r.With(write).Post("/domains", h.CreateMailDomain)
|
|
r.With(read).Get("/domains/{domainID}", h.GetMailDomain)
|
|
r.With(write).Post("/domains/{domainID}/verify-txt", h.VerifyMailDomainTXT)
|
|
r.With(write).Post("/domains/{domainID}/verify-mx", h.VerifyMailDomainMX)
|
|
}
|
|
})
|
|
r.Route("/migration", func(r chi.Router) {
|
|
if h.svc.migration == nil {
|
|
return
|
|
}
|
|
r.With(read).Get("/projects", h.ListMigrationProjects)
|
|
r.With(write).Post("/projects", h.CreateMigrationProject)
|
|
r.With(write).Post("/projects/{projectID}/activate", h.ActivateMigrationProject)
|
|
r.With(read).Get("/projects/{projectID}/cutover-dns", h.PreflightMigrationCutoverDNS)
|
|
r.With(write).Post("/projects/{projectID}/cutover", h.StartMigrationCutover)
|
|
r.With(write).Post("/projects/{projectID}/invites", h.CreateMigrationInvite)
|
|
r.With(write).Post("/projects/{projectID}/invites/import", h.ImportMigrationInvites)
|
|
r.With(read).Get("/projects/{projectID}/roster", h.ListMigrationRoster)
|
|
r.With(write).Post("/projects/{projectID}/roster", h.ImportMigrationRoster)
|
|
r.With(read).Get("/projects/{projectID}/jobs", h.ListMigrationProjectJobs)
|
|
r.With(read).Get("/projects/{projectID}/jobs/{jobID}/audit", h.ListMigrationJobAudit)
|
|
r.With(read).Get("/projects/{projectID}/jobs/{jobID}/audit/summary", h.MigrationJobAuditSummary)
|
|
r.With(read).Get("/projects/{projectID}/jobs/{jobID}/audit/export", h.ExportMigrationJobAudit)
|
|
r.With(read).Get("/projects/{projectID}/audit/export", h.ExportMigrationProjectAudit)
|
|
r.With(write).Post("/projects/{projectID}/jobs/retry-failed", h.RetryMigrationFailedJobs)
|
|
r.With(write).Post("/projects/{projectID}/jobs/{jobID}/retry", h.RetryMigrationJob)
|
|
r.With(write).Post("/projects/{projectID}/jobs/{jobID}/reset-cursor", h.ResetMigrationJobCursor)
|
|
r.With(write).Patch("/projects/{projectID}/shared-drive-mode", h.UpdateMigrationSharedDriveMode)
|
|
r.With(read).Get("/projects/{projectID}/shared-drives", h.ListMigrationSharedDrives)
|
|
r.With(write).Post("/projects/{projectID}/shared-drives/{driveID}/approve", h.ApproveMigrationSharedDrive)
|
|
r.With(write).Post("/projects/{projectID}/shared-drives/{driveID}/reject", h.RejectMigrationSharedDrive)
|
|
r.With(read).Get("/microsoft/admin-consent-url", h.MicrosoftMigrationAdminConsentURL)
|
|
r.With(read).Get("/microsoft/admin-consents", h.ListMicrosoftAdminConsents)
|
|
})
|
|
}
|
|
|
|
func (h *Handler) ListMailDomains(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.svc.hosted.ListDomains(r.Context())
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"domains": rows})
|
|
}
|
|
|
|
type createMailDomainRequest struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (h *Handler) CreateMailDomain(w http.ResponseWriter, r *http.Request) {
|
|
var req createMailDomainRequest
|
|
if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
|
|
return
|
|
}
|
|
row, err := h.svc.hosted.CreateDomain(r.Context(), req.Name, false)
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusCreated, row)
|
|
}
|
|
|
|
func (h *Handler) GetMailDomain(w http.ResponseWriter, r *http.Request) {
|
|
row, err := h.svc.hosted.GetDomain(r.Context(), chi.URLParam(r, "domainID"))
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, row)
|
|
}
|
|
|
|
func (h *Handler) VerifyMailDomainTXT(w http.ResponseWriter, r *http.Request) {
|
|
domainID := chi.URLParam(r, "domainID")
|
|
row, report, err := h.svc.hosted.VerifyDomainTXTRecord(r.Context(), domainID)
|
|
if err != nil {
|
|
apiresponse.WriteError(w, r, http.StatusBadRequest, "dns_txt_not_verified", err.Error(), map[string]any{"dns": report})
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"domain": row, "dns": report})
|
|
}
|
|
|
|
func (h *Handler) VerifyMailDomainMX(w http.ResponseWriter, r *http.Request) {
|
|
domainID := chi.URLParam(r, "domainID")
|
|
expected := h.migrationCutoverConfig().ExpectedMXHosts
|
|
row, report, err := h.svc.hosted.VerifyDomainMXRecord(r.Context(), domainID, expected)
|
|
if err != nil {
|
|
apiresponse.WriteError(w, r, http.StatusBadRequest, "dns_mx_not_verified", err.Error(), map[string]any{"dns": report})
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"domain": row, "dns": report})
|
|
}
|
|
|
|
type createMigrationProjectRequest struct {
|
|
Name string `json:"name"`
|
|
SourceProvider string `json:"source_provider"`
|
|
DomainID string `json:"domain_id"`
|
|
AuthMode string `json:"auth_mode"`
|
|
}
|
|
|
|
func (h *Handler) CreateMigrationProject(w http.ResponseWriter, r *http.Request) {
|
|
var req createMigrationProjectRequest
|
|
if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
|
|
return
|
|
}
|
|
row, err := h.svc.migration.CreateProject(r.Context(), req.Name, req.SourceProvider, req.DomainID, req.AuthMode)
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusCreated, row)
|
|
}
|
|
|
|
func (h *Handler) ListMigrationProjects(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.svc.migration.ListProjects(r.Context())
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"projects": rows})
|
|
}
|
|
|
|
func (h *Handler) ActivateMigrationProject(w http.ResponseWriter, r *http.Request) {
|
|
row, err := h.svc.migration.ActivateProject(r.Context(), chi.URLParam(r, "projectID"))
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, row)
|
|
}
|
|
|
|
func (h *Handler) PreflightMigrationCutoverDNS(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
report, err := h.svc.migration.PreflightCutoverDNS(r.Context(), chi.URLParam(r, "projectID"), h.migrationCutoverConfig())
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"dns": report})
|
|
}
|
|
|
|
func (h *Handler) StartMigrationCutover(w http.ResponseWriter, r *http.Request) {
|
|
result, err := h.svc.migration.StartCutover(r.Context(), chi.URLParam(r, "projectID"))
|
|
if errors.Is(err, migr.ErrCutoverMXNotReady) {
|
|
apiresponse.WriteError(w, r, http.StatusConflict, "migration_cutover_mx_not_ready", err.Error(), map[string]any{
|
|
"dns": result.DNS,
|
|
"project": result.Project,
|
|
})
|
|
return
|
|
}
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, result)
|
|
}
|
|
|
|
func (h *Handler) migrationCutoverConfig() migr.CutoverConfig {
|
|
if h.svc.cfg == nil {
|
|
return migr.CutoverConfig{}
|
|
}
|
|
return migr.CutoverConfig{
|
|
ExpectedMXHosts: migr.ParseCutoverMXHosts(
|
|
h.svc.cfg.MigrationCutoverMXHosts,
|
|
h.svc.cfg.PlatformMailDomain,
|
|
h.svc.cfg.StalwartIMAPHost,
|
|
),
|
|
RequireMX: h.svc.cfg.MigrationCutoverRequireMX,
|
|
}
|
|
}
|
|
|
|
type createMigrationInviteRequest struct {
|
|
Email string `json:"email"`
|
|
AlternateEmails []string `json:"alternate_emails,omitempty"`
|
|
}
|
|
|
|
func (h *Handler) CreateMigrationInvite(w http.ResponseWriter, r *http.Request) {
|
|
var req createMigrationInviteRequest
|
|
if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
|
|
return
|
|
}
|
|
row, err := h.svc.migration.CreateInvite(r.Context(), chi.URLParam(r, "projectID"), req.Email, req.AlternateEmails)
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusCreated, row)
|
|
}
|
|
|
|
func (h *Handler) ImportMigrationInvites(w http.ResponseWriter, r *http.Request) {
|
|
var emails []string
|
|
contentType := r.Header.Get("Content-Type")
|
|
if strings.Contains(contentType, "multipart/form-data") {
|
|
file, _, err := r.FormFile("file")
|
|
if err == nil {
|
|
defer file.Close()
|
|
reader := csv.NewReader(file)
|
|
for {
|
|
record, err := reader.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "file", Message: "invalid csv",
|
|
}))
|
|
return
|
|
}
|
|
if len(record) > 0 {
|
|
emails = append(emails, record[0])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(emails) == 0 {
|
|
var body struct {
|
|
Emails []string `json:"emails"`
|
|
}
|
|
if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &body); err != nil {
|
|
return
|
|
}
|
|
emails = body.Emails
|
|
}
|
|
count, err := h.svc.migration.ImportInvites(r.Context(), chi.URLParam(r, "projectID"), emails)
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"imported": count})
|
|
}
|
|
|
|
func (h *Handler) ListMigrationRoster(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
rows, err := h.svc.migration.ListRoster(r.Context(), chi.URLParam(r, "projectID"))
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
if rows == nil {
|
|
rows = []migr.RosterEntry{}
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"roster": rows})
|
|
}
|
|
|
|
func (h *Handler) ImportMigrationRoster(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
projectID := chi.URLParam(r, "projectID")
|
|
|
|
var inputs []migr.RosterRowInput
|
|
contentType := r.Header.Get("Content-Type")
|
|
if strings.Contains(contentType, "multipart/form-data") {
|
|
file, _, err := r.FormFile("file")
|
|
if err == nil {
|
|
defer file.Close()
|
|
parsed, err := migr.ParseRosterCSV(file)
|
|
if err != nil {
|
|
apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "file", Message: err.Error(),
|
|
}))
|
|
return
|
|
}
|
|
inputs = parsed
|
|
}
|
|
}
|
|
if len(inputs) == 0 {
|
|
var body struct {
|
|
CSV string `json:"csv"`
|
|
Rows []migr.RosterRowInput `json:"rows"`
|
|
}
|
|
if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &body); err != nil {
|
|
return
|
|
}
|
|
if len(body.Rows) > 0 {
|
|
inputs = body.Rows
|
|
} else if strings.TrimSpace(body.CSV) != "" {
|
|
parsed, err := migr.ParseRosterCSV(strings.NewReader(body.CSV))
|
|
if err != nil {
|
|
apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "csv", Message: err.Error(),
|
|
}))
|
|
return
|
|
}
|
|
inputs = parsed
|
|
}
|
|
}
|
|
if len(inputs) == 0 {
|
|
apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(apivalidate.FieldDetail{
|
|
Field: "csv", Message: "roster csv or rows required",
|
|
}))
|
|
return
|
|
}
|
|
|
|
result, err := h.svc.migration.ImportRoster(r.Context(), projectID, inputs)
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, result)
|
|
}
|
|
|
|
func (h *Handler) MicrosoftMigrationAdminConsentURL(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
consentURL, err := h.svc.migration.MicrosoftAdminConsentURL(
|
|
r.URL.Query().Get("tenant"),
|
|
r.URL.Query().Get("project_id"),
|
|
)
|
|
if err != nil {
|
|
apiresponse.WriteError(w, r, http.StatusBadRequest, "admin_consent_unavailable", err.Error(), nil)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"url": consentURL})
|
|
}
|
|
|
|
func (h *Handler) ListMicrosoftAdminConsents(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
rows, err := h.svc.migration.ListMicrosoftAdminConsents(r.Context())
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"consents": rows})
|
|
}
|
|
|
|
func (h *Handler) ListMigrationProjectJobs(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
rows, err := h.svc.migration.ListProjectJobs(r.Context(), chi.URLParam(r, "projectID"))
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"jobs": rows})
|
|
}
|
|
|
|
func (h *Handler) RetryMigrationJob(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
row, err := h.svc.migration.RetryJob(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "jobID"))
|
|
if err != nil {
|
|
apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_retryable", err.Error(), nil)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, row)
|
|
}
|
|
|
|
func (h *Handler) ResetMigrationJobCursor(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
row, err := h.svc.migration.ResetJobCursor(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "jobID"))
|
|
if err != nil {
|
|
status := http.StatusNotFound
|
|
code := "migration_job_not_resettable"
|
|
if strings.Contains(err.Error(), "running") {
|
|
status = http.StatusConflict
|
|
} else if strings.Contains(err.Error(), "not found") {
|
|
code = "migration_job_not_found"
|
|
}
|
|
apiresponse.WriteError(w, r, status, code, err.Error(), nil)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, row)
|
|
}
|
|
|
|
func (h *Handler) RetryMigrationFailedJobs(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
count, err := h.svc.migration.RetryFailedJobs(r.Context(), chi.URLParam(r, "projectID"))
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"retried": count})
|
|
}
|
|
|
|
func (h *Handler) ListMigrationJobAudit(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
params, err := query.ParseListRequest(r)
|
|
if err != nil {
|
|
apivalidate.WriteQueryError(w, r, err)
|
|
return
|
|
}
|
|
items, pagination, err := h.svc.migration.ListJobAudit(
|
|
r.Context(),
|
|
chi.URLParam(r, "projectID"),
|
|
chi.URLParam(r, "jobID"),
|
|
r.URL.Query().Get("status"),
|
|
params,
|
|
)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "not found") {
|
|
apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_found", err.Error(), nil)
|
|
return
|
|
}
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{
|
|
"items": items,
|
|
"pagination": pagination,
|
|
})
|
|
}
|
|
|
|
func (h *Handler) MigrationJobAuditSummary(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
summary, err := h.svc.migration.JobAuditSummary(
|
|
r.Context(),
|
|
chi.URLParam(r, "projectID"),
|
|
chi.URLParam(r, "jobID"),
|
|
)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "not found") {
|
|
apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_found", err.Error(), nil)
|
|
return
|
|
}
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, summary)
|
|
}
|
|
|
|
func (h *Handler) ExportMigrationJobAudit(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
format, verr := validateExportFormat(r.URL.Query().Get("format"))
|
|
if verr != nil {
|
|
apivalidate.WriteValidationError(w, r, verr)
|
|
return
|
|
}
|
|
|
|
projectID := chi.URLParam(r, "projectID")
|
|
jobID := chi.URLParam(r, "jobID")
|
|
meta, err := h.svc.migration.PrepareJobAuditExport(r.Context(), projectID, jobID, format)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "not found") {
|
|
apiresponse.WriteError(w, r, http.StatusNotFound, "migration_job_not_found", err.Error(), nil)
|
|
return
|
|
}
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", meta.ContentType)
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, meta.FileName))
|
|
w.WriteHeader(http.StatusOK)
|
|
if err := h.svc.migration.WriteJobAuditExport(
|
|
r.Context(),
|
|
projectID,
|
|
jobID,
|
|
r.URL.Query().Get("status"),
|
|
format,
|
|
w,
|
|
); err != nil {
|
|
h.logger.Error("export migration job audit", "error", err)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) ExportMigrationProjectAudit(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
format, verr := validateExportFormat(r.URL.Query().Get("format"))
|
|
if verr != nil {
|
|
apivalidate.WriteValidationError(w, r, verr)
|
|
return
|
|
}
|
|
|
|
projectID := chi.URLParam(r, "projectID")
|
|
meta, err := h.svc.migration.PrepareProjectAuditExport(r.Context(), projectID, format)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "not found") {
|
|
apiresponse.WriteError(w, r, http.StatusNotFound, "migration_project_not_found", err.Error(), nil)
|
|
return
|
|
}
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", meta.ContentType)
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, meta.FileName))
|
|
w.WriteHeader(http.StatusOK)
|
|
if err := h.svc.migration.WriteProjectAuditExport(
|
|
r.Context(),
|
|
projectID,
|
|
r.URL.Query().Get("status"),
|
|
format,
|
|
w,
|
|
); err != nil {
|
|
h.logger.Error("export migration project audit", "error", err)
|
|
}
|
|
}
|
|
|
|
type updateSharedDriveModeRequest struct {
|
|
Mode string `json:"shared_drive_mode"`
|
|
}
|
|
|
|
func (h *Handler) UpdateMigrationSharedDriveMode(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
var req updateSharedDriveModeRequest
|
|
if err := apivalidate.DecodeJSON(w, r, maxAdminMailRequestBody, &req); err != nil {
|
|
return
|
|
}
|
|
row, err := h.svc.migration.UpdateSharedDriveMode(r.Context(), chi.URLParam(r, "projectID"), req.Mode)
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, row)
|
|
}
|
|
|
|
func (h *Handler) ListMigrationSharedDrives(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
rows, err := h.svc.migration.ListSharedDrives(r.Context(), chi.URLParam(r, "projectID"), r.URL.Query().Get("status"))
|
|
if err != nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"shared_drives": rows})
|
|
}
|
|
|
|
func (h *Handler) ApproveMigrationSharedDrive(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
row, err := h.svc.migration.ApproveSharedDrive(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "driveID"))
|
|
if err != nil {
|
|
apiresponse.WriteError(w, r, http.StatusNotFound, "shared_drive_not_found", err.Error(), nil)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, row)
|
|
}
|
|
|
|
func (h *Handler) RejectMigrationSharedDrive(w http.ResponseWriter, r *http.Request) {
|
|
if h.svc.migration == nil {
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
row, err := h.svc.migration.RejectSharedDrive(r.Context(), chi.URLParam(r, "projectID"), chi.URLParam(r, "driveID"))
|
|
if err != nil {
|
|
apiresponse.WriteError(w, r, http.StatusNotFound, "shared_drive_not_found", err.Error(), nil)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, row)
|
|
}
|