mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 15:26:36 +08:00
fix(zone): register domain delete/update APIs and drop edit UI
补全 Zone 与 Zone 域名的 update/delete 路由与业务逻辑,修复删除域名 404;前端域名列表移除编辑入口,仅保留添加与删除。
This commit is contained in:
@@ -25,7 +25,12 @@ func BindJSON(c *gin.Context, dst any) bool {
|
||||
|
||||
// IDParam parses :id from the URL path.
|
||||
func IDParam(c *gin.Context) (uint, bool) {
|
||||
raw := c.Param("id")
|
||||
return NamedIDParam(c, "id")
|
||||
}
|
||||
|
||||
// NamedIDParam parses a named path parameter as a positive uint ID.
|
||||
func NamedIDParam(c *gin.Context, name string) (uint, bool) {
|
||||
raw := c.Param(name)
|
||||
if raw == "" {
|
||||
response.AbortBadRequest(c, errInvalidID)
|
||||
return 0, false
|
||||
|
||||
@@ -13,4 +13,6 @@ const (
|
||||
errDomainNotFound = "域名不存在"
|
||||
errDomainExists = "域名已存在"
|
||||
errCertificateNotFound = "所选证书不存在"
|
||||
errDomainBoundToRoute = "域名已绑定反代路由,请先解除绑定"
|
||||
errZoneHasDomains = "Zone 下仍有域名,请先删除全部域名"
|
||||
)
|
||||
|
||||
@@ -222,6 +222,34 @@ func UpdateDomain(ctx context.Context, zoneID, id uint, input DomainInput) (*mod
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
// DeleteDomain removes a Zone domain that is not bound to a proxy route.
|
||||
func DeleteDomain(ctx context.Context, zoneID, id uint) error {
|
||||
var item model.ZoneDomain
|
||||
if err := db.DB(ctx).Where("id = ? AND zone_id = ?", id, zoneID).First(&item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if item.ProxyRouteID != nil {
|
||||
return errors.New(errDomainBoundToRoute)
|
||||
}
|
||||
return db.DB(ctx).Delete(&item).Error
|
||||
}
|
||||
|
||||
// Delete removes a Zone that has no remaining domains.
|
||||
func Delete(ctx context.Context, id uint) error {
|
||||
var zone model.Zone
|
||||
if err := db.DB(ctx).First(&zone, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var count int64
|
||||
if err := db.DB(ctx).Model(&model.ZoneDomain{}).Where("zone_id = ?", id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New(errZoneHasDomains)
|
||||
}
|
||||
return db.DB(ctx).Delete(&zone).Error
|
||||
}
|
||||
|
||||
func isUnique(err error) bool {
|
||||
return errors.Is(err, gorm.ErrDuplicatedKey) || strings.Contains(strings.ToLower(err.Error()), "unique constraint")
|
||||
}
|
||||
|
||||
@@ -32,6 +32,24 @@ func TestCreateZoneDomainRejectsWildcard(t *testing.T) {
|
||||
require.EqualError(t, err, errDomainWildcardUnsupported)
|
||||
}
|
||||
|
||||
func TestDeleteDomainRejectsBoundRoute(t *testing.T) {
|
||||
ctx := setupZoneDB(t)
|
||||
zone, err := Create(ctx, Input{Domain: "example.com"})
|
||||
require.NoError(t, err)
|
||||
item, err := CreateDomain(ctx, zone.ID, DomainInput{Domain: "api.example.com"})
|
||||
require.NoError(t, err)
|
||||
routeID := uint(9)
|
||||
item.ProxyRouteID = &routeID
|
||||
require.NoError(t, db.DB(ctx).Save(item).Error)
|
||||
|
||||
err = DeleteDomain(ctx, zone.ID, item.ID)
|
||||
require.EqualError(t, err, errDomainBoundToRoute)
|
||||
|
||||
item.ProxyRouteID = nil
|
||||
require.NoError(t, db.DB(ctx).Save(item).Error)
|
||||
require.NoError(t, DeleteDomain(ctx, zone.ID, item.ID))
|
||||
}
|
||||
|
||||
func TestLegacyImportUsesEffectiveTLDPlusOne(t *testing.T) {
|
||||
root, err := zoneRoot("api.example.co.uk")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -87,6 +87,56 @@ func GetOverviewHandler(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response.OK(item))
|
||||
}
|
||||
|
||||
// UpdateHandler updates a Zone.
|
||||
// @Summary 更新 Zone
|
||||
// @Tags openflare-zone
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "Zone ID"
|
||||
// @Param body body zone.Input true "Zone 参数"
|
||||
// @Success 200 {object} response.Any{data=model.Zone}
|
||||
// @Failure 400 {object} response.Any
|
||||
// @Failure 404 {object} response.Any
|
||||
// @Failure 409 {object} response.Any
|
||||
// @Router /api/v1/d/zones/{id}/update [post]
|
||||
func UpdateHandler(c *gin.Context) {
|
||||
id, ok := apiutil.IDParam(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input Input
|
||||
if !apiutil.BindJSON(c, &input) {
|
||||
return
|
||||
}
|
||||
item, err := Update(c.Request.Context(), id, input)
|
||||
if abort(c, err, errZoneNotFound) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(item))
|
||||
}
|
||||
|
||||
// DeleteHandler deletes a Zone with no remaining domains.
|
||||
// @Summary 删除 Zone
|
||||
// @Tags openflare-zone
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "Zone ID"
|
||||
// @Success 200 {object} response.Any
|
||||
// @Failure 400 {object} response.Any
|
||||
// @Failure 404 {object} response.Any
|
||||
// @Router /api/v1/d/zones/{id}/delete [post]
|
||||
func DeleteHandler(c *gin.Context) {
|
||||
id, ok := apiutil.IDParam(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := Delete(c.Request.Context(), id); abort(c, err, errZoneNotFound) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
// CreateDomainHandler creates an explicit FQDN under a Zone.
|
||||
// @Summary 创建 Zone 域名
|
||||
// @Tags openflare-zone
|
||||
@@ -115,3 +165,63 @@ func CreateDomainHandler(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(item))
|
||||
}
|
||||
|
||||
// UpdateDomainHandler updates a Zone domain.
|
||||
// @Summary 更新 Zone 域名
|
||||
// @Tags openflare-zone
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "Zone ID"
|
||||
// @Param domainId path int true "域名 ID"
|
||||
// @Param body body zone.DomainInput true "域名参数"
|
||||
// @Success 200 {object} response.Any{data=model.ZoneDomain}
|
||||
// @Failure 400 {object} response.Any
|
||||
// @Failure 404 {object} response.Any
|
||||
// @Failure 409 {object} response.Any
|
||||
// @Router /api/v1/d/zones/{id}/domains/{domainId}/update [post]
|
||||
func UpdateDomainHandler(c *gin.Context) {
|
||||
zoneID, ok := apiutil.IDParam(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
domainID, ok := apiutil.NamedIDParam(c, "domainId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input DomainInput
|
||||
if !apiutil.BindJSON(c, &input) {
|
||||
return
|
||||
}
|
||||
item, err := UpdateDomain(c.Request.Context(), zoneID, domainID, input)
|
||||
if abort(c, err, errDomainNotFound) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OK(item))
|
||||
}
|
||||
|
||||
// DeleteDomainHandler deletes a Zone domain not bound to a proxy route.
|
||||
// @Summary 删除 Zone 域名
|
||||
// @Tags openflare-zone
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path int true "Zone ID"
|
||||
// @Param domainId path int true "域名 ID"
|
||||
// @Success 200 {object} response.Any
|
||||
// @Failure 400 {object} response.Any
|
||||
// @Failure 404 {object} response.Any
|
||||
// @Router /api/v1/d/zones/{id}/domains/{domainId}/delete [post]
|
||||
func DeleteDomainHandler(c *gin.Context) {
|
||||
zoneID, ok := apiutil.IDParam(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
domainID, ok := apiutil.NamedIDParam(c, "domainId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := DeleteDomain(c.Request.Context(), zoneID, domainID); abort(c, err, errDomainNotFound) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response.OKNil())
|
||||
}
|
||||
|
||||
@@ -15,5 +15,9 @@ func registerZoneRoutes(apiGroup *gin.RouterGroup) {
|
||||
apiutil.RegisterCollection(zoneGroup, "GET", zone.ListHandler)
|
||||
apiutil.RegisterCollection(zoneGroup, "POST", zone.CreateHandler)
|
||||
zoneGroup.GET("/:id/overview", zone.GetOverviewHandler)
|
||||
zoneGroup.POST("/:id/update", zone.UpdateHandler)
|
||||
zoneGroup.POST("/:id/delete", zone.DeleteHandler)
|
||||
zoneGroup.POST("/:id/domains", zone.CreateDomainHandler)
|
||||
zoneGroup.POST("/:id/domains/:domainId/update", zone.UpdateDomainHandler)
|
||||
zoneGroup.POST("/:id/domains/:domainId/delete", zone.DeleteDomainHandler)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user