feat: Enhance import handler to support mounted Caddyfile and improve conflict reporting
This commit is contained in:
@@ -100,7 +100,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Register import handler with config dependencies
|
||||
routes.RegisterImportHandler(router, db, cfg.CaddyBinary, cfg.ImportDir)
|
||||
routes.RegisterImportHandler(router, db, cfg.CaddyBinary, cfg.ImportDir, cfg.ImportCaddyfile)
|
||||
|
||||
// Check for mounted Caddyfile on startup
|
||||
if err := handlers.CheckMountedImport(db, cfg.ImportCaddyfile, cfg.CaddyBinary, cfg.ImportDir); err != nil {
|
||||
|
||||
@@ -24,15 +24,17 @@ type ImportHandler struct {
|
||||
proxyHostSvc *services.ProxyHostService
|
||||
importerservice *caddy.Importer
|
||||
importDir string
|
||||
mountPath string
|
||||
}
|
||||
|
||||
// NewImportHandler creates a new import handler.
|
||||
func NewImportHandler(db *gorm.DB, caddyBinary, importDir string) *ImportHandler {
|
||||
func NewImportHandler(db *gorm.DB, caddyBinary, importDir, mountPath string) *ImportHandler {
|
||||
return &ImportHandler{
|
||||
db: db,
|
||||
proxyHostSvc: services.NewProxyHostService(db),
|
||||
importerservice: caddy.NewImporter(caddyBinary),
|
||||
importDir: importDir,
|
||||
mountPath: mountPath,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,42 +88,77 @@ func (h *ImportHandler) GetPreview(c *gin.Context) {
|
||||
}
|
||||
|
||||
var result caddy.ImportResult
|
||||
if err := json.Unmarshal([]byte(session.ParsedData), &result); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse import data"})
|
||||
if err := json.Unmarshal([]byte(session.ParsedData), &result); err == nil {
|
||||
// Update status to reviewing
|
||||
session.Status = "reviewing"
|
||||
h.db.Save(&session)
|
||||
|
||||
// Read original Caddyfile content if available
|
||||
var caddyfileContent string
|
||||
if session.SourceFile != "" {
|
||||
if content, err := os.ReadFile(session.SourceFile); err == nil {
|
||||
caddyfileContent = string(content)
|
||||
} else {
|
||||
backupPath := filepath.Join(h.importDir, "backups", filepath.Base(session.SourceFile))
|
||||
if content, err := os.ReadFile(backupPath); err == nil {
|
||||
caddyfileContent = string(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"session": gin.H{
|
||||
"id": session.UUID,
|
||||
"state": session.Status,
|
||||
"created_at": session.CreatedAt,
|
||||
"updated_at": session.UpdatedAt,
|
||||
"source_file": session.SourceFile,
|
||||
},
|
||||
"preview": result,
|
||||
"caddyfile_content": caddyfileContent,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Update status to reviewing
|
||||
session.Status = "reviewing"
|
||||
h.db.Save(&session)
|
||||
// No DB session found or failed to parse session. Try transient preview from mountPath.
|
||||
if h.mountPath != "" {
|
||||
if _, err := os.Stat(h.mountPath); err == nil {
|
||||
// Parse mounted Caddyfile transiently
|
||||
transient, err := h.importerservice.ImportFile(h.mountPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse mounted Caddyfile"})
|
||||
return
|
||||
}
|
||||
|
||||
// Read original Caddyfile content if available
|
||||
var caddyfileContent string
|
||||
if session.SourceFile != "" {
|
||||
// Try to read from the source file path (if it's a mounted file)
|
||||
if content, err := os.ReadFile(session.SourceFile); err == nil {
|
||||
caddyfileContent = string(content)
|
||||
} else {
|
||||
// If source file not readable (e.g. uploaded temp file deleted), try to find backup
|
||||
// This is a best-effort attempt
|
||||
backupPath := filepath.Join(h.importDir, "backups", filepath.Base(session.SourceFile))
|
||||
if content, err := os.ReadFile(backupPath); err == nil {
|
||||
// Build a transient session id (not persisted)
|
||||
sid := uuid.NewString()
|
||||
var caddyfileContent string
|
||||
if content, err := os.ReadFile(h.mountPath); err == nil {
|
||||
caddyfileContent = string(content)
|
||||
}
|
||||
|
||||
// Check for conflicts with existing hosts and append raw domain names
|
||||
existingHosts, _ := h.proxyHostSvc.List()
|
||||
existingDomains := make(map[string]bool)
|
||||
for _, eh := range existingHosts {
|
||||
existingDomains[eh.DomainNames] = true
|
||||
}
|
||||
for _, ph := range transient.Hosts {
|
||||
if existingDomains[ph.DomainNames] {
|
||||
transient.Conflicts = append(transient.Conflicts, ph.DomainNames)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"session": gin.H{"id": sid, "state": "transient", "source_file": h.mountPath},
|
||||
"preview": transient,
|
||||
"caddyfile_content": caddyfileContent,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"session": gin.H{
|
||||
"id": session.UUID,
|
||||
"state": session.Status,
|
||||
"created_at": session.CreatedAt,
|
||||
"updated_at": session.UpdatedAt,
|
||||
"source_file": session.SourceFile,
|
||||
},
|
||||
"preview": result,
|
||||
"caddyfile_content": caddyfileContent,
|
||||
})
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "no pending import"})
|
||||
}
|
||||
|
||||
// Upload handles manual Caddyfile upload or paste.
|
||||
@@ -136,25 +173,43 @@ func (h *ImportHandler) Upload(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create temporary file
|
||||
tempPath := filepath.Join(h.importDir, fmt.Sprintf("upload-%s.caddyfile", uuid.NewString()))
|
||||
if err := os.MkdirAll(h.importDir, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create import directory"})
|
||||
// Save upload to import/uploads/<uuid>.caddyfile and return transient preview (do not persist yet)
|
||||
sid := uuid.NewString()
|
||||
uploadsDir := filepath.Join(h.importDir, "uploads")
|
||||
if err := os.MkdirAll(uploadsDir, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create uploads directory"})
|
||||
return
|
||||
}
|
||||
|
||||
tempPath := filepath.Join(uploadsDir, fmt.Sprintf("%s.caddyfile", sid))
|
||||
if err := os.WriteFile(tempPath, []byte(req.Content), 0644); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to write upload"})
|
||||
return
|
||||
}
|
||||
|
||||
// Process the uploaded file
|
||||
if err := h.processImport(tempPath, req.Filename); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
// Parse uploaded file transiently
|
||||
result, err := h.importerservice.ImportFile(tempPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("import failed: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "upload processed, ready for review"})
|
||||
// Check for conflicts with existing hosts and append raw domain names
|
||||
existingHosts, _ := h.proxyHostSvc.List()
|
||||
existingDomains := make(map[string]bool)
|
||||
for _, eh := range existingHosts {
|
||||
existingDomains[eh.DomainNames] = true
|
||||
}
|
||||
for _, ph := range result.Hosts {
|
||||
if existingDomains[ph.DomainNames] {
|
||||
result.Conflicts = append(result.Conflicts, ph.DomainNames)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"session": gin.H{"id": sid, "state": "transient", "source_file": tempPath},
|
||||
"preview": result,
|
||||
})
|
||||
}
|
||||
|
||||
// Commit finalizes the import with user's conflict resolutions.
|
||||
@@ -169,16 +224,44 @@ func (h *ImportHandler) Commit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to find a DB-backed session first
|
||||
var session models.ImportSession
|
||||
if err := h.db.Where("uuid = ? AND status = ?", req.SessionUUID, "reviewing").First(&session).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "session not found or not in reviewing state"})
|
||||
return
|
||||
}
|
||||
|
||||
var result caddy.ImportResult
|
||||
if err := json.Unmarshal([]byte(session.ParsedData), &result); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse import data"})
|
||||
return
|
||||
var result *caddy.ImportResult
|
||||
if err := h.db.Where("uuid = ? AND status = ?", req.SessionUUID, "reviewing").First(&session).Error; err == nil {
|
||||
// DB session found
|
||||
if err := json.Unmarshal([]byte(session.ParsedData), &result); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse import data"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// No DB session: check for uploaded temp file
|
||||
uploadsPath := filepath.Join(h.importDir, "uploads", fmt.Sprintf("%s.caddyfile", req.SessionUUID))
|
||||
if _, err := os.Stat(uploadsPath); err == nil {
|
||||
r, err := h.importerservice.ImportFile(uploadsPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse uploaded file"})
|
||||
return
|
||||
}
|
||||
result = r
|
||||
// We'll create a committed DB session after applying
|
||||
session = models.ImportSession{UUID: req.SessionUUID, SourceFile: uploadsPath}
|
||||
} else if h.mountPath != "" {
|
||||
if _, err := os.Stat(h.mountPath); err == nil {
|
||||
r, err := h.importerservice.ImportFile(h.mountPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to parse mounted Caddyfile"})
|
||||
return
|
||||
}
|
||||
result = r
|
||||
session = models.ImportSession{UUID: req.SessionUUID, SourceFile: h.mountPath}
|
||||
} else {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "session not found or file missing"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "session not found"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Convert parsed hosts to ProxyHost models
|
||||
@@ -213,12 +296,21 @@ func (h *ImportHandler) Commit(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark session as committed
|
||||
// Persist an import session record now that user confirmed
|
||||
now := time.Now()
|
||||
session.Status = "committed"
|
||||
session.CommittedAt = &now
|
||||
session.UserResolutions = string(mustMarshal(req.Resolutions))
|
||||
h.db.Save(&session)
|
||||
// If ParsedData/ConflictReport not set, fill from result
|
||||
if session.ParsedData == "" {
|
||||
session.ParsedData = string(mustMarshal(result))
|
||||
}
|
||||
if session.ConflictReport == "" {
|
||||
session.ConflictReport = string(mustMarshal(result.Conflicts))
|
||||
}
|
||||
if err := h.db.Save(&session).Error; err != nil {
|
||||
log.Printf("Warning: failed to save import session: %v", err)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"created": created,
|
||||
@@ -236,15 +328,23 @@ func (h *ImportHandler) Cancel(c *gin.Context) {
|
||||
}
|
||||
|
||||
var session models.ImportSession
|
||||
if err := h.db.Where("uuid = ?", sessionUUID).First(&session).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "session not found"})
|
||||
if err := h.db.Where("uuid = ?", sessionUUID).First(&session).Error; err == nil {
|
||||
session.Status = "rejected"
|
||||
h.db.Save(&session)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "import cancelled"})
|
||||
return
|
||||
}
|
||||
|
||||
session.Status = "rejected"
|
||||
h.db.Save(&session)
|
||||
// If no DB session, check for uploaded temp file and delete it
|
||||
uploadsPath := filepath.Join(h.importDir, "uploads", fmt.Sprintf("%s.caddyfile", sessionUUID))
|
||||
if _, err := os.Stat(uploadsPath); err == nil {
|
||||
os.Remove(uploadsPath)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "transient upload cancelled"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "import cancelled"})
|
||||
// If neither exists, return not found
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "session not found"})
|
||||
}
|
||||
|
||||
// processImport handles the import logic for both mounted and uploaded files.
|
||||
@@ -269,8 +369,8 @@ func (h *ImportHandler) processImport(caddyfilePath, originalName string) error
|
||||
|
||||
for _, parsed := range result.Hosts {
|
||||
if existingDomains[parsed.DomainNames] {
|
||||
result.Conflicts = append(result.Conflicts,
|
||||
fmt.Sprintf("Domain '%s' already exists in CPM+", parsed.DomainNames))
|
||||
// Append the raw domain name so frontend can match conflicts against domain strings
|
||||
result.Conflicts = append(result.Conflicts, parsed.DomainNames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,10 +399,12 @@ func (h *ImportHandler) processImport(caddyfilePath, originalName string) error
|
||||
// CheckMountedImport checks for mounted Caddyfile on startup.
|
||||
func CheckMountedImport(db *gorm.DB, mountPath, caddyBinary, importDir string) error {
|
||||
if _, err := os.Stat(mountPath); os.IsNotExist(err) {
|
||||
return nil // No mounted file, skip
|
||||
// If mount is gone, remove any pending/reviewing sessions created previously for this mount
|
||||
db.Where("source_file = ? AND status IN ?", mountPath, []string{"pending", "reviewing"}).Delete(&models.ImportSession{})
|
||||
return nil // No mounted file, nothing to import
|
||||
}
|
||||
|
||||
// Check if already processed
|
||||
// Check if already processed (includes committed to avoid re-imports)
|
||||
var count int64
|
||||
db.Model(&models.ImportSession{}).Where("source_file = ? AND status IN ?",
|
||||
mountPath, []string{"pending", "reviewing", "committed"}).Count(&count)
|
||||
@@ -311,8 +413,8 @@ func CheckMountedImport(db *gorm.DB, mountPath, caddyBinary, importDir string) e
|
||||
return nil // Already processed
|
||||
}
|
||||
|
||||
handler := NewImportHandler(db, caddyBinary, importDir)
|
||||
return handler.processImport(mountPath, mountPath)
|
||||
// Do not create a DB session automatically for mounted imports; preview will be transient.
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustMarshal(v interface{}) []byte {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -34,7 +35,7 @@ func TestImportHandler_GetStatus(t *testing.T) {
|
||||
db := setupImportTestDB(t)
|
||||
|
||||
// Case 1: No active session
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
router.GET("/import/status", handler.GetStatus)
|
||||
|
||||
@@ -68,7 +69,7 @@ func TestImportHandler_GetStatus(t *testing.T) {
|
||||
func TestImportHandler_GetPreview(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
router.GET("/import/preview", handler.GetPreview)
|
||||
|
||||
@@ -107,7 +108,7 @@ func TestImportHandler_GetPreview(t *testing.T) {
|
||||
func TestImportHandler_Cancel(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
router.DELETE("/import/cancel", handler.Cancel)
|
||||
|
||||
@@ -131,7 +132,7 @@ func TestImportHandler_Cancel(t *testing.T) {
|
||||
func TestImportHandler_Commit(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
router.POST("/import/commit", handler.Commit)
|
||||
|
||||
@@ -178,7 +179,7 @@ func TestImportHandler_Upload(t *testing.T) {
|
||||
os.Chmod(fakeCaddy, 0755)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir)
|
||||
handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, "")
|
||||
router := gin.New()
|
||||
router.POST("/import/upload", handler.Upload)
|
||||
|
||||
@@ -205,7 +206,7 @@ func TestImportHandler_GetPreview_WithContent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
tmpDir := t.TempDir()
|
||||
handler := handlers.NewImportHandler(db, "echo", tmpDir)
|
||||
handler := handlers.NewImportHandler(db, "echo", tmpDir, "")
|
||||
router := gin.New()
|
||||
router.GET("/import/preview", handler.GetPreview)
|
||||
|
||||
@@ -239,7 +240,7 @@ func TestImportHandler_GetPreview_WithContent(t *testing.T) {
|
||||
func TestImportHandler_Commit_Errors(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
router.POST("/import/commit", handler.Commit)
|
||||
|
||||
@@ -282,7 +283,7 @@ func TestImportHandler_Commit_Errors(t *testing.T) {
|
||||
func TestImportHandler_Cancel_Errors(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
router.DELETE("/import/cancel", handler.Cancel)
|
||||
|
||||
@@ -314,10 +315,10 @@ func TestCheckMountedImport(t *testing.T) {
|
||||
err = handlers.CheckMountedImport(db, mountPath, fakeCaddy, tmpDir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if session created
|
||||
// Check if session created (transient preview behavior: no DB session should be created)
|
||||
var count int64
|
||||
db.Model(&models.ImportSession{}).Where("source_file = ?", mountPath).Count(&count)
|
||||
assert.Equal(t, int64(1), count)
|
||||
assert.Equal(t, int64(0), count)
|
||||
|
||||
// Case 3: Already processed
|
||||
err = handlers.CheckMountedImport(db, mountPath, fakeCaddy, tmpDir)
|
||||
@@ -333,7 +334,7 @@ func TestImportHandler_Upload_Failure(t *testing.T) {
|
||||
fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy_fail.sh")
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir)
|
||||
handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, "")
|
||||
router := gin.New()
|
||||
router.POST("/import/upload", handler.Upload)
|
||||
|
||||
@@ -370,7 +371,7 @@ func TestImportHandler_Upload_Conflict(t *testing.T) {
|
||||
fakeCaddy := filepath.Join(cwd, "testdata", "fake_caddy_hosts.sh")
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir)
|
||||
handler := handlers.NewImportHandler(db, fakeCaddy, tmpDir, "")
|
||||
router := gin.New()
|
||||
router.POST("/import/upload", handler.Upload)
|
||||
|
||||
@@ -386,18 +387,27 @@ func TestImportHandler_Upload_Conflict(t *testing.T) {
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Verify session created with conflict
|
||||
var session models.ImportSession
|
||||
db.First(&session)
|
||||
assert.Equal(t, "pending", session.Status)
|
||||
assert.Contains(t, session.ConflictReport, "Domain 'example.com' already exists")
|
||||
// Verify response contains conflict in preview (upload is transient)
|
||||
var resp map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.NoError(t, err)
|
||||
preview := resp["preview"].(map[string]interface{})
|
||||
conflicts := preview["conflicts"].([]interface{})
|
||||
found := false
|
||||
for _, c := range conflicts {
|
||||
if c.(string) == "example.com" || strings.Contains(c.(string), "example.com") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "expected conflict for example.com in preview")
|
||||
}
|
||||
|
||||
func TestImportHandler_GetPreview_BackupContent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
tmpDir := t.TempDir()
|
||||
handler := handlers.NewImportHandler(db, "echo", tmpDir)
|
||||
handler := handlers.NewImportHandler(db, "echo", tmpDir, "")
|
||||
router := gin.New()
|
||||
router.GET("/import/preview", handler.GetPreview)
|
||||
|
||||
@@ -430,7 +440,7 @@ func TestImportHandler_GetPreview_BackupContent(t *testing.T) {
|
||||
|
||||
func TestImportHandler_RegisterRoutes(t *testing.T) {
|
||||
db := setupImportTestDB(t)
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
api := router.Group("/api/v1")
|
||||
handler.RegisterRoutes(api)
|
||||
@@ -445,7 +455,7 @@ func TestImportHandler_RegisterRoutes(t *testing.T) {
|
||||
func TestImportHandler_Errors(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupImportTestDB(t)
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp")
|
||||
handler := handlers.NewImportHandler(db, "echo", "/tmp", "")
|
||||
router := gin.New()
|
||||
router.POST("/import/upload", handler.Upload)
|
||||
router.POST("/import/commit", handler.Commit)
|
||||
|
||||
@@ -214,8 +214,8 @@ func Register(router *gin.Engine, db *gorm.DB, cfg config.Config) error {
|
||||
}
|
||||
|
||||
// RegisterImportHandler wires up import routes with config dependencies.
|
||||
func RegisterImportHandler(router *gin.Engine, db *gorm.DB, caddyBinary, importDir string) {
|
||||
importHandler := handlers.NewImportHandler(db, caddyBinary, importDir)
|
||||
func RegisterImportHandler(router *gin.Engine, db *gorm.DB, caddyBinary, importDir, mountPath string) {
|
||||
importHandler := handlers.NewImportHandler(db, caddyBinary, importDir, mountPath)
|
||||
api := router.Group("/api/v1")
|
||||
importHandler.RegisterRoutes(api)
|
||||
}
|
||||
|
||||
@@ -139,10 +139,9 @@ func (i *Importer) ExtractHosts(caddyJSON []byte) (*ImportResult, error) {
|
||||
for _, hostMatcher := range match.Host {
|
||||
domain := hostMatcher
|
||||
|
||||
// Check for duplicate domains
|
||||
// Check for duplicate domains (report domain names only)
|
||||
if seenDomains[domain] {
|
||||
result.Conflicts = append(result.Conflicts,
|
||||
fmt.Sprintf("Duplicate domain detected: %s", domain))
|
||||
result.Conflicts = append(result.Conflicts, domain)
|
||||
continue
|
||||
}
|
||||
seenDomains[domain] = true
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestImporter_ExtractHosts(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result.Hosts, 1)
|
||||
assert.Len(t, result.Conflicts, 1)
|
||||
assert.Contains(t, result.Conflicts[0], "Duplicate domain detected")
|
||||
assert.Equal(t, "example.com", result.Conflicts[0])
|
||||
|
||||
// Test Case 5: Unsupported Features
|
||||
unsupportedJSON := []byte(`{
|
||||
|
||||
@@ -36,8 +36,8 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # For local container discovery
|
||||
- ./backend:/app/backend:ro # Mount source for debugging
|
||||
# Mount your existing Caddyfile for automatic import (optional)
|
||||
# - ./my-existing-Caddyfile:/import/Caddyfile:ro
|
||||
# - ./sites:/import/sites:ro # If your Caddyfile imports other files
|
||||
# - /root/docker/containers/caddy/Caddyfile:/import/Caddyfile:ro
|
||||
# - /root/docker/containers/caddy/sites:/import/sites:ro # If your Caddyfile imports other files
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/v1/health"]
|
||||
interval: 30s
|
||||
|
||||
Reference in New Issue
Block a user