Add zone diff summary endpoint for efficient diff count retrieval

This commit is contained in:
nemunaire 2026-01-26 11:51:47 +08:00
commit 30a3f7ec3e
4 changed files with 89 additions and 41 deletions

View file

@ -94,6 +94,45 @@ func (zc *ZoneController) GetZoneSubdomain(c *gin.Context) {
})
}
// DiffZonesHandler is a middleware that computes the differences between two zones.
// It retrieves corrections between the zone in context and either the currently deployed
// zone (when oldzoneid is "@") or another zone identifier. The computed corrections and
// difference count are stored in the context for use by subsequent handlers.
func (zc *ZoneController) DiffZonesHandler(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
domain := c.MustGet("domain").(*happydns.Domain)
newzone := c.MustGet("zone").(*happydns.Zone)
var nbDiffs int
var corrections []*happydns.Correction
if c.Param("oldzoneid") == "@" {
var err error
corrections, nbDiffs, err = zc.zoneCorrectionService.List(user, domain, newzone)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
} else {
oldzoneid, err := middleware.ParseZoneId(c, "oldzoneid")
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
corrections, err = zc.zoneService.DiffZones(domain, newzone, oldzoneid)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
nbDiffs = len(corrections)
}
c.Set("corrections", corrections)
c.Set("nbDiffs", nbDiffs)
c.Next()
}
// DiffZones computes the difference between the two zone identifiers given.
//
// @Summary Compute differences between zones.
@ -114,35 +153,36 @@ func (zc *ZoneController) GetZoneSubdomain(c *gin.Context) {
// @Failure 501 {object} happydns.ErrorResponse "Diff between to zone identifier, currently not supported"
// @Router /domains/{domainId}/zone/{zoneId}/diff/{oldZoneId} [post]
func (zc *ZoneController) DiffZones(c *gin.Context) {
user := c.MustGet("LoggedUser").(*happydns.User)
domain := c.MustGet("domain").(*happydns.Domain)
newzone := c.MustGet("zone").(*happydns.Zone)
var corrections []*happydns.Correction
if c.Param("oldzoneid") == "@" {
var err error
corrections, _, err = zc.zoneCorrectionService.List(user, domain, newzone)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
} else {
oldzoneid, err := middleware.ParseZoneId(c, "oldzoneid")
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
corrections, err = zc.zoneService.DiffZones(domain, newzone, oldzoneid)
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
}
corrections := c.MustGet("corrections").([]*happydns.Correction)
c.JSON(http.StatusOK, corrections)
}
// DiffZonesSummary computes the number of differences between two zones.
//
// @Summary Get summary of differences between zones.
// @Schemes
// @Description Compute the number of differences between the two zone identifiers given, without returning the full diff.
// @Tags zones
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Param domainId path string true "Domain identifier"
// @Param zoneId path string true "Zone identifier to use as the new one."
// @Param oldZoneId path string true "Zone identifier to use as the old one. Currently only @ are expected, to use the currently deployed zone."
// @Success 200 {object} object{nbDiffs=int} "Summary containing the number of differences"
// @Failure 400 {object} happydns.ErrorResponse "Invalid input"
// @Failure 401 {object} happydns.ErrorResponse "Authentication failure"
// @Failure 404 {object} happydns.ErrorResponse "Domain not found"
// @Failure 500 {object} happydns.ErrorResponse
// @Failure 501 {object} happydns.ErrorResponse "Diff between to zone identifier, currently not supported"
// @Router /domains/{domainId}/zone/{zoneId}/diff/{oldZoneId}/summary [post]
func (zc *ZoneController) DiffZonesSummary(c *gin.Context) {
nbDiffs := c.MustGet("nbDiffs").(int)
c.JSON(http.StatusOK, gin.H{"nbDiffs": nbDiffs})
}
// ApplyZoneCorrections performs the requested changes with the provider.
//
// @Summary Performs requested changes to the real zone.

View file

@ -26,7 +26,7 @@ import (
"git.happydns.org/happyDomain/internal/api/controller"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
happydns "git.happydns.org/happyDomain/model"
)
func DeclareZoneRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
@ -41,7 +41,8 @@ func DeclareZoneRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDep
apiZonesRoutes.GET("", zc.GetZone)
apiZonesRoutes.POST("/diff/:oldzoneid", zc.DiffZones)
apiZonesRoutes.POST("/diff/:oldzoneid", zc.DiffZonesHandler, zc.DiffZones)
apiZonesRoutes.POST("/diff/:oldzoneid/summary", zc.DiffZonesHandler, zc.DiffZonesSummary)
apiZonesRoutes.POST("/view", zc.ExportZone)
apiZonesRoutes.POST("/apply_changes", zc.ApplyZoneCorrections)

View file

@ -26,6 +26,7 @@ import {
postDomainsByDomainIdZoneByZoneIdApplyChanges,
postDomainsByDomainIdZone,
postDomainsByDomainIdZoneByZoneIdDiffByOldZoneId,
postDomainsByDomainIdZoneByZoneIdDiffByOldZoneIdSummary,
postDomainsByDomainIdZoneByZoneIdBySubdomainServices,
patchDomainsByDomainIdZoneByZoneId,
deleteDomainsByDomainIdZoneByZoneIdBySubdomainServicesByServiceId,
@ -103,6 +104,18 @@ export async function diffZone(
) as Array<Correction>;
}
export async function diffZoneSummary(
domain: Domain,
id1: string,
id2: string,
): Promise<{ nbDiffs: number }> {
return unwrapSdkResponse(
await postDomainsByDomainIdZoneByZoneIdDiffByOldZoneIdSummary({
path: { domainId: domain.id, zoneId: id2, oldZoneId: id1 },
}),
) as { nbDiffs: number };
}
export async function addZoneService(
domain: Domain,
id: string,

View file

@ -22,21 +22,16 @@
-->
<script lang="ts">
import {
Button,
Icon,
Spinner,
} from "@sveltestrap/sveltestrap";
import { Button, Icon, Spinner } from "@sveltestrap/sveltestrap";
import { getDomain as APIGetDomain } from "$lib/api/domains";
import { controls as ctrlDiffZone } from "./ModalDiffZone.svelte";
import { controls as ctrlDomainDelete } from "./ModalDomainDelete.svelte";
import { diffZone as APIDiffZone } from "$lib/api/zone";
import DiffSummary from "./DiffSummary.svelte";
import { diffZoneSummary as APIDiffZoneSummary } from "$lib/api/zone";
import type { Domain } from "$lib/model/domain";
import { domains_idx } from "$lib/stores/domains";
import { thisZone } from "$lib/stores/thiszone";
import { t } from "$lib/translations";
import { controls as ctrlDiffZone } from "./ModalDiffZone.svelte";
import { controls as ctrlDomainDelete } from "./ModalDomainDelete.svelte";
interface Props {
domain: Domain;
@ -61,8 +56,9 @@
{#if $domains_idx[domain.id] && $thisZone}
{#if $domains_idx[domain.id].zone_history && history === $domains_idx[domain.id].zone_history[0]}
{#key $thisZone}
{#await APIDiffZone(domain, "@", $thisZone.id)}
{#await APIDiffZoneSummary(domain, "@", $thisZone.id)}
<Button
class="mt-2 mb-3"
size="lg"
color="success"
outline
@ -72,23 +68,21 @@
<Spinner size="sm" />
{$t("domains.actions.propagate")}
</Button>
<p class="mt-2 mb-1 text-center">
{$t("wait.wait")}
</p>
{:then zoneDiff}
{:then zoneDiffSummary}
<Button
class="mt-2 mb-3"
size="lg"
color="success"
outline={zoneDiff && !zoneDiff.length}
outline={zoneDiffSummary && !zoneDiffSummary.nbDiffs}
title={$t("domains.actions.propagate")}
on:click={showDiff}
>
<Icon name="cloud-upload" aria-hidden="true" />
{$t("domains.actions.propagate")}
{#if zoneDiffSummary && zoneDiffSummary.nbDiffs}
<small class="text-muted">({zoneDiffSummary.nbDiffs})</small>
{/if}
</Button>
<p class="mt-2 mb-1 text-center">
<DiffSummary {zoneDiff} />
</p>
{:catch err}
<p class="mb-0 text-center">
<Icon name="exclamation-triangle" class="text-danger" />