Introduce a DomainAvailabilityWatch entity (model, storage, usecase and REST endpoints) letting a user track a domain they do not own and get notified the moment it becomes available for registration. A dedicated domain_availability checker reads WHOIS/RDAP via pkg/domaininfo and inverts the status (OK while registered, Crit once free) so the existing dispatcher fires exactly once on the transition. The scheduler enumerates watches and enqueues the check, carrying the watch id in CheckTarget.DomainId; autofill and notification payloads fall back to the watch store to resolve the name. Watches are included in per-user backup/restore. The web UI adds an availability watchlist page and navigation entry.
180 lines
6.3 KiB
Go
180 lines
6.3 KiB
Go
// This file is part of the happyDomain (R) project.
|
|
// Copyright (c) 2020-2026 happyDomain
|
|
// Authors: Pierre-Olivier Mercier, et al.
|
|
//
|
|
// This program is offered under a commercial and under the AGPL license.
|
|
// For commercial licensing, contact us at <contact@happydomain.org>.
|
|
//
|
|
// For AGPL licensing:
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"git.happydns.org/happyDomain/internal/api/middleware"
|
|
"git.happydns.org/happyDomain/model"
|
|
)
|
|
|
|
type DomainAvailabilityWatchController struct {
|
|
watchService happydns.DomainAvailabilityWatchUsecase
|
|
}
|
|
|
|
func NewDomainAvailabilityWatchController(watchService happydns.DomainAvailabilityWatchUsecase) *DomainAvailabilityWatchController {
|
|
return &DomainAvailabilityWatchController{
|
|
watchService: watchService,
|
|
}
|
|
}
|
|
|
|
// ListDomainAvailabilityWatches retrieves all availability watches owned by the user.
|
|
//
|
|
// @Summary Retrieve user's availability watches
|
|
// @Schemes
|
|
// @Description Retrieve all domain availability watches belonging to the user.
|
|
// @Tags availability
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security securitydefinitions.basic
|
|
// @Success 200 {array} happydns.DomainAvailabilityWatch
|
|
// @Failure 401 {object} happydns.ErrorResponse "Authentication failure"
|
|
// @Failure 500 {object} happydns.ErrorResponse "Unable to retrieve watches"
|
|
// @Router /availability [get]
|
|
func (wc *DomainAvailabilityWatchController) ListDomainAvailabilityWatches(c *gin.Context) {
|
|
user := middleware.MyUser(c)
|
|
if user == nil {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "User not defined"})
|
|
return
|
|
}
|
|
|
|
watches, err := wc.watchService.ListUserDomainAvailabilityWatches(user)
|
|
if err != nil {
|
|
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, watches)
|
|
}
|
|
|
|
// AddDomainAvailabilityWatch registers a new availability watch.
|
|
//
|
|
// @Summary Add a new availability watch
|
|
// @Schemes
|
|
// @Description Register a new domain availability watch for the user.
|
|
// @Tags availability
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body happydns.DomainAvailabilityWatchCreationInput true "Watch to add"
|
|
// @Security securitydefinitions.basic
|
|
// @Success 200 {object} happydns.DomainAvailabilityWatch
|
|
// @Failure 400 {object} happydns.ErrorResponse "Error in received data"
|
|
// @Failure 401 {object} happydns.ErrorResponse "Authentication failure"
|
|
// @Failure 500 {object} happydns.ErrorResponse "Database writing error"
|
|
// @Router /availability [post]
|
|
func (wc *DomainAvailabilityWatchController) AddDomainAvailabilityWatch(c *gin.Context) {
|
|
user := middleware.MyUser(c)
|
|
if user == nil {
|
|
middleware.ErrorResponse(c, http.StatusBadRequest, fmt.Errorf("No user specified."))
|
|
return
|
|
}
|
|
|
|
var input happydns.DomainAvailabilityWatchCreationInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to decode given watch: %s", err.Error())})
|
|
return
|
|
}
|
|
|
|
watch, err := wc.watchService.CreateDomainAvailabilityWatch(c.Request.Context(), user, &input)
|
|
if err != nil {
|
|
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, watch)
|
|
}
|
|
|
|
// GetDomainAvailabilityWatch retrieves a single availability watch owned by the user.
|
|
//
|
|
// @Summary Retrieve an availability watch
|
|
// @Schemes
|
|
// @Description Retrieve a single domain availability watch owned by the user.
|
|
// @Tags availability
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param watchId path string true "Watch identifier"
|
|
// @Security securitydefinitions.basic
|
|
// @Success 200 {object} happydns.DomainAvailabilityWatch
|
|
// @Failure 401 {object} happydns.ErrorResponse "Authentication failure"
|
|
// @Failure 404 {object} happydns.ErrorResponse "Watch not found"
|
|
// @Router /availability/{watchId} [get]
|
|
func (wc *DomainAvailabilityWatchController) GetDomainAvailabilityWatch(c *gin.Context) {
|
|
user := middleware.MyUser(c)
|
|
if user == nil {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "User not defined"})
|
|
return
|
|
}
|
|
|
|
id, err := happydns.NewIdentifierFromString(c.Param("watchId"))
|
|
if err != nil {
|
|
middleware.ErrorResponse(c, http.StatusBadRequest, fmt.Errorf("Invalid watch identifier: %w", err))
|
|
return
|
|
}
|
|
|
|
watch, err := wc.watchService.GetUserDomainAvailabilityWatch(user, id)
|
|
if err != nil {
|
|
middleware.ErrorResponse(c, http.StatusNotFound, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, watch)
|
|
}
|
|
|
|
// DeleteDomainAvailabilityWatch removes an availability watch owned by the user.
|
|
//
|
|
// @Summary Delete an availability watch
|
|
// @Schemes
|
|
// @Description Delete a domain availability watch owned by the user.
|
|
// @Tags availability
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param watchId path string true "Watch identifier"
|
|
// @Security securitydefinitions.basic
|
|
// @Success 204 "Watch deleted"
|
|
// @Failure 401 {object} happydns.ErrorResponse "Authentication failure"
|
|
// @Failure 404 {object} happydns.ErrorResponse "Watch not found"
|
|
// @Failure 500 {object} happydns.ErrorResponse "Database writing error"
|
|
// @Router /availability/{watchId} [delete]
|
|
func (wc *DomainAvailabilityWatchController) DeleteDomainAvailabilityWatch(c *gin.Context) {
|
|
user := middleware.MyUser(c)
|
|
if user == nil {
|
|
middleware.ErrorResponse(c, http.StatusBadRequest, fmt.Errorf("User not defined."))
|
|
return
|
|
}
|
|
|
|
id, err := happydns.NewIdentifierFromString(c.Param("watchId"))
|
|
if err != nil {
|
|
middleware.ErrorResponse(c, http.StatusBadRequest, fmt.Errorf("Invalid watch identifier: %w", err))
|
|
return
|
|
}
|
|
|
|
if err := wc.watchService.DeleteDomainAvailabilityWatch(user, id); err != nil {
|
|
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
|
|
c.Status(http.StatusNoContent)
|
|
}
|