api-admin: domain controller delegates to AdminDomainUsecase
Add an AdminDomainUsecase interface for the cross-user operations the admin API needs (list every domain, fetch any by ID or by FQDN+owner, raw create/update, clear all) and implement it on domain.Service. Refactor the admin DomainController to depend on DomainUsecase + AdminDomainUsecase only, dropping its direct domain.DomainStorage handle. Per-user clears now go through DomainUsecase.DeleteDomain, which already encapsulates the deletion side effects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
126f78498c
commit
55c4237f6e
7 changed files with 112 additions and 30 deletions
|
|
@ -31,28 +31,27 @@ import (
|
|||
|
||||
"git.happydns.org/happyDomain/internal/api/controller"
|
||||
"git.happydns.org/happyDomain/internal/api/middleware"
|
||||
"git.happydns.org/happyDomain/internal/usecase/domain"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
happydns "git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
type DomainController struct {
|
||||
domainService happydns.DomainUsecase
|
||||
adminService happydns.AdminDomainUsecase
|
||||
remoteZoneImporter happydns.RemoteZoneImporterUsecase
|
||||
zoneImporter happydns.ZoneImporterUsecase
|
||||
store domain.DomainStorage
|
||||
}
|
||||
|
||||
func NewDomainController(
|
||||
duService happydns.DomainUsecase,
|
||||
adminService happydns.AdminDomainUsecase,
|
||||
remoteZoneImporter happydns.RemoteZoneImporterUsecase,
|
||||
zoneImporter happydns.ZoneImporterUsecase,
|
||||
store domain.DomainStorage,
|
||||
) *DomainController {
|
||||
return &DomainController{
|
||||
duService,
|
||||
remoteZoneImporter,
|
||||
zoneImporter,
|
||||
store,
|
||||
domainService: duService,
|
||||
adminService: adminService,
|
||||
remoteZoneImporter: remoteZoneImporter,
|
||||
zoneImporter: zoneImporter,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,17 +78,11 @@ func (dc *DomainController) ListDomains(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
iter, err := dc.store.ListAllDomains()
|
||||
domains, err := dc.adminService.ListAllDomains()
|
||||
if err != nil {
|
||||
middleware.ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("unable to retrieve domains list: %w", err))
|
||||
return
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var domains []*happydns.Domain
|
||||
for iter.Next() {
|
||||
domains = append(domains, iter.Item())
|
||||
}
|
||||
|
||||
happydns.ApiResponse(c, domains, nil)
|
||||
}
|
||||
|
|
@ -123,7 +116,7 @@ func (dc *DomainController) NewDomain(c *gin.Context) {
|
|||
ud.Id = nil
|
||||
ud.Owner = user.Id
|
||||
|
||||
happydns.ApiResponse(c, ud, dc.store.CreateDomain(ud))
|
||||
happydns.ApiResponse(c, ud, dc.adminService.AdminCreateDomain(ud))
|
||||
}
|
||||
|
||||
// DeleteDomain removes a domain from the system by identifier or domain name.
|
||||
|
|
@ -156,7 +149,7 @@ func (dc *DomainController) DeleteDomain(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
domains, err := dc.store.GetDomainByDN(user, c.Param("domain"))
|
||||
domains, err := dc.adminService.GetDomainsByFQDN(user, c.Param("domain"))
|
||||
if err != nil {
|
||||
middleware.ErrorResponse(c, http.StatusNotFound, err)
|
||||
return
|
||||
|
|
@ -170,19 +163,17 @@ func (dc *DomainController) DeleteDomain(c *gin.Context) {
|
|||
domainid = domains[0].Id
|
||||
}
|
||||
|
||||
happydns.ApiResponse(c, true, dc.store.DeleteDomain(domainid))
|
||||
happydns.ApiResponse(c, true, dc.domainService.DeleteDomain(domainid))
|
||||
}
|
||||
|
||||
func (dc *DomainController) searchUserDomain(filter func(*happydns.Domain) bool) *happydns.User {
|
||||
iter, err := dc.store.ListAllDomains()
|
||||
domains, err := dc.adminService.ListAllDomains()
|
||||
if err != nil {
|
||||
log.Println("Unable to retrieve domains list:", err.Error())
|
||||
return nil
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Next() {
|
||||
domain := iter.Item()
|
||||
for _, domain := range domains {
|
||||
if filter(domain) {
|
||||
// Create a fake minimal user, as only the Id is required to perform further actions on database
|
||||
return &happydns.User{Id: domain.Owner}
|
||||
|
|
@ -223,7 +214,7 @@ func (dc *DomainController) GetDomain(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
domain, err := dc.store.GetDomainByDN(user, c.Param("domain"))
|
||||
domain, err := dc.adminService.GetDomainsByFQDN(user, c.Param("domain"))
|
||||
happydns.ApiResponse(c, domain, err)
|
||||
} else {
|
||||
var user *happydns.User
|
||||
|
|
@ -236,7 +227,7 @@ func (dc *DomainController) GetDomain(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
domain, err := dc.store.GetDomain(domainid)
|
||||
domain, err := dc.adminService.GetDomainByID(domainid)
|
||||
if err != nil {
|
||||
happydns.ApiResponse(c, nil, err)
|
||||
return
|
||||
|
|
@ -281,7 +272,7 @@ func (dc *DomainController) UpdateDomain(c *gin.Context) {
|
|||
}
|
||||
ud.Id = domain.Id
|
||||
|
||||
happydns.ApiResponse(c, ud, dc.store.UpdateDomain(ud))
|
||||
happydns.ApiResponse(c, ud, dc.adminService.AdminUpdateDomain(ud))
|
||||
}
|
||||
|
||||
// ClearDomains removes all domains from the system or all domains belonging to a specific user.
|
||||
|
|
@ -309,8 +300,7 @@ func (dc *DomainController) ClearDomains(c *gin.Context) {
|
|||
}
|
||||
|
||||
for _, dn := range domains {
|
||||
e := dc.store.DeleteDomain(dn.Id)
|
||||
if e != nil {
|
||||
if e := dc.domainService.DeleteDomain(dn.Id); e != nil {
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
}
|
||||
|
|
@ -324,7 +314,7 @@ func (dc *DomainController) ClearDomains(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
happydns.ApiResponse(c, true, dc.store.ClearDomains())
|
||||
happydns.ApiResponse(c, true, dc.adminService.ClearDomains())
|
||||
}
|
||||
|
||||
// UpdateZones updates the zone history for a specific domain.
|
||||
|
|
@ -355,5 +345,5 @@ func (dc *DomainController) UpdateZones(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
happydns.ApiResponse(c, domain, dc.store.UpdateDomain(domain))
|
||||
happydns.ApiResponse(c, domain, dc.adminService.AdminUpdateDomain(domain))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ import (
|
|||
func declareDomainRoutes(router *gin.RouterGroup, dep Dependencies, store storage.Storage) {
|
||||
dc := controller.NewDomainController(
|
||||
dep.Domain,
|
||||
dep.AdminDomain,
|
||||
dep.RemoteZoneImporter,
|
||||
dep.ZoneImporter,
|
||||
store,
|
||||
)
|
||||
|
||||
router.GET("/domains", dc.ListDomains)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
// Dependencies holds all use cases required to register the admin API routes.
|
||||
type Dependencies struct {
|
||||
AdminAuthUser happydns.AdminAuthUserUsecase
|
||||
AdminDomain happydns.AdminDomainUsecase
|
||||
AdminProvider happydns.AdminProviderUsecase
|
||||
AdminSession happydns.AdminSessionUsecase
|
||||
AdminUser happydns.AdminUserUsecase
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ func NewAdmin(app *App) *Admin {
|
|||
app.store,
|
||||
admin.Dependencies{
|
||||
AdminAuthUser: app.usecases.authUserAdmin,
|
||||
AdminDomain: app.usecases.domainAdmin,
|
||||
AdminProvider: providerAdminService,
|
||||
AdminSession: sessionUC.NewService(app.store),
|
||||
AdminUser: app.usecases.userAdmin,
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ type Usecases struct {
|
|||
authUser happydns.AuthUserUsecase
|
||||
authUserAdmin happydns.AdminAuthUserUsecase
|
||||
domain happydns.DomainUsecase
|
||||
domainAdmin happydns.AdminDomainUsecase
|
||||
domainInfo happydns.DomainInfoUsecase
|
||||
domainLog happydns.DomainLogUsecase
|
||||
emailAutoconfig happydns.EmailAutoconfigUsecase
|
||||
|
|
@ -261,6 +262,7 @@ func (app *App) initUsecases() {
|
|||
domainLogService,
|
||||
)
|
||||
app.usecases.domain = domainService
|
||||
app.usecases.domainAdmin = domainService
|
||||
app.usecases.zoneService = zoneServiceUC.NewZoneServiceUsecases(
|
||||
domainService,
|
||||
zoneService.CreateZoneUC,
|
||||
|
|
|
|||
75
internal/usecase/domain/admin.go
Normal file
75
internal/usecase/domain/admin.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// 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 domain
|
||||
|
||||
import (
|
||||
happydns "git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
// ListAllDomains returns every domain in the system. Intended for
|
||||
// administrative callers; iterator drainage is hidden from the caller.
|
||||
func (s *Service) ListAllDomains() ([]*happydns.Domain, error) {
|
||||
iter, err := s.store.ListAllDomains()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var domains []*happydns.Domain
|
||||
for iter.Next() {
|
||||
domains = append(domains, iter.Item())
|
||||
}
|
||||
return domains, iter.Err()
|
||||
}
|
||||
|
||||
// GetDomainByID retrieves a domain by its identifier without ownership
|
||||
// verification. Intended for administrative callers.
|
||||
func (s *Service) GetDomainByID(domainID happydns.Identifier) (*happydns.Domain, error) {
|
||||
return s.store.GetDomain(domainID)
|
||||
}
|
||||
|
||||
// GetDomainsByFQDN looks up domains owned by user that match the given
|
||||
// fully-qualified domain name. Intended for administrative callers that
|
||||
// have already resolved the owner.
|
||||
func (s *Service) GetDomainsByFQDN(user *happydns.User, fqdn string) ([]*happydns.Domain, error) {
|
||||
return s.store.GetDomainByDN(user, fqdn)
|
||||
}
|
||||
|
||||
// AdminCreateDomain persists domain as-is, bypassing the registration-time
|
||||
// validations performed by CreateDomain.
|
||||
func (s *Service) AdminCreateDomain(domain *happydns.Domain) error {
|
||||
return s.store.CreateDomain(domain)
|
||||
}
|
||||
|
||||
// AdminUpdateDomain persists changes to domain. It is the write-side
|
||||
// counterpart used by admin endpoints that mutate a Domain in memory and
|
||||
// need to commit the result without going through the user-scoped
|
||||
// fetch-mutate-save cycle of UpdateDomain.
|
||||
func (s *Service) AdminUpdateDomain(domain *happydns.Domain) error {
|
||||
return s.store.UpdateDomain(domain)
|
||||
}
|
||||
|
||||
// ClearDomains removes every domain from the database. Intended for
|
||||
// administrative callers performing a full reset.
|
||||
func (s *Service) ClearDomains() error {
|
||||
return s.store.ClearDomains()
|
||||
}
|
||||
|
|
@ -130,3 +130,16 @@ type DomainUsecase interface {
|
|||
ListUserDomains(*User) ([]*Domain, error)
|
||||
UpdateDomain(Identifier, *User, func(*Domain)) error
|
||||
}
|
||||
|
||||
// AdminDomainUsecase exposes administrative domain operations that bypass
|
||||
// the caller-scoped checks of DomainUsecase. Admin callers can list every
|
||||
// domain, fetch any domain by ID or by FQDN+owner, create or replace one
|
||||
// from a raw Domain, and wipe the table.
|
||||
type AdminDomainUsecase interface {
|
||||
ListAllDomains() ([]*Domain, error)
|
||||
GetDomainByID(Identifier) (*Domain, error)
|
||||
GetDomainsByFQDN(*User, string) ([]*Domain, error)
|
||||
AdminCreateDomain(*Domain) error
|
||||
AdminUpdateDomain(*Domain) error
|
||||
ClearDomains() error
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue