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.
98 lines
3.7 KiB
Go
98 lines
3.7 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 happydns
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// DomainAvailabilityWatch tracks a domain name the User does NOT own, so they
|
|
// can be notified the moment it becomes available for registration. Unlike a
|
|
// Domain, a watch is not tied to any Provider and never manages a zone.
|
|
type DomainAvailabilityWatch struct {
|
|
// Id is the watch's identifier in the database.
|
|
Id Identifier `json:"id" swaggertype:"string" binding:"required" readonly:"true"`
|
|
|
|
// Owner is the identifier of the User watching the domain.
|
|
Owner Identifier `json:"id_owner" swaggertype:"string" binding:"required" readonly:"true"`
|
|
|
|
// DomainName is the FQDN being watched for availability.
|
|
DomainName string `json:"domain" binding:"required"`
|
|
|
|
// Interval optionally overrides how often the availability check runs.
|
|
// When nil, the checker's default interval is used.
|
|
Interval *time.Duration `json:"interval,omitempty" swaggertype:"integer"`
|
|
|
|
// CreatedAt records when the watch was registered.
|
|
CreatedAt time.Time `json:"created_at" readonly:"true"`
|
|
}
|
|
|
|
// DomainAvailabilityWatchCreationInput is used for swagger documentation as
|
|
// availability watch add.
|
|
type DomainAvailabilityWatchCreationInput struct {
|
|
// DomainName is the FQDN to watch for availability.
|
|
DomainName string `json:"domain" binding:"required"`
|
|
|
|
// Interval optionally overrides the default check interval.
|
|
Interval *time.Duration `json:"interval,omitempty" swaggertype:"integer"`
|
|
}
|
|
|
|
// NewDomainAvailabilityWatch validates the name and builds a watch owned by the
|
|
// given user.
|
|
func NewDomainAvailabilityWatch(user *User, name string) (*DomainAvailabilityWatch, error) {
|
|
name = dns.Fqdn(strings.TrimSpace(name))
|
|
|
|
if name == "." {
|
|
return nil, errors.New("empty domain name")
|
|
}
|
|
|
|
if _, ok := dns.IsDomainName(name); !ok {
|
|
return nil, errors.New("invalid domain name")
|
|
}
|
|
|
|
return &DomainAvailabilityWatch{
|
|
Owner: user.Id,
|
|
DomainName: name,
|
|
CreatedAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// DomainAvailabilityWatchUsecase exposes owner-scoped operations on the
|
|
// availability watchlist.
|
|
type DomainAvailabilityWatchUsecase interface {
|
|
CreateDomainAvailabilityWatch(context.Context, *User, *DomainAvailabilityWatchCreationInput) (*DomainAvailabilityWatch, error)
|
|
DeleteDomainAvailabilityWatch(*User, Identifier) error
|
|
GetUserDomainAvailabilityWatch(*User, Identifier) (*DomainAvailabilityWatch, error)
|
|
ListUserDomainAvailabilityWatches(*User) ([]*DomainAvailabilityWatch, error)
|
|
}
|
|
|
|
// SchedulerWatchNotifier is an optional callback to notify the scheduler about
|
|
// availability-watch changes so it can incrementally update its job queue.
|
|
type SchedulerWatchNotifier interface {
|
|
NotifyWatchChange(watch *DomainAvailabilityWatch)
|
|
NotifyWatchRemoved(watchID Identifier)
|
|
}
|