mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-05-06 22:12:23 +08:00
311 lines
7.1 KiB
Go
311 lines
7.1 KiB
Go
package dns
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/samber/lo"
|
|
"github.com/spf13/cast"
|
|
"github.com/uozi-tech/cosy"
|
|
|
|
"github.com/0xJacky/Nginx-UI/internal/cron"
|
|
dnsService "github.com/0xJacky/Nginx-UI/internal/dns"
|
|
"github.com/0xJacky/Nginx-UI/model"
|
|
)
|
|
|
|
func ListDomains(c *gin.Context) {
|
|
cosy.Core[model.DnsDomain](c).
|
|
SetPreloads("DnsCredential").
|
|
SetFussy("domain", "description").
|
|
PagingList()
|
|
}
|
|
|
|
func GetDomain(c *gin.Context) {
|
|
cosy.Core[model.DnsDomain](c).
|
|
SetPreloads("DnsCredential").
|
|
Get()
|
|
}
|
|
|
|
func CreateDomain(c *gin.Context) {
|
|
cosy.Core[model.DnsDomain](c).
|
|
SetValidRules(gin.H{
|
|
"domain": "required",
|
|
"description": "omitempty",
|
|
"dns_credential_id": "required",
|
|
}).
|
|
BeforeExecuteHook(domainMutationHook(dnsService.NewService(), false)).
|
|
Create()
|
|
}
|
|
|
|
func UpdateDomain(c *gin.Context) {
|
|
cosy.Core[model.DnsDomain](c).
|
|
SetValidRules(gin.H{
|
|
"domain": "required",
|
|
"description": "omitempty",
|
|
"dns_credential_id": "required",
|
|
}).
|
|
BeforeExecuteHook(domainMutationHook(dnsService.NewService(), true)).
|
|
Modify()
|
|
}
|
|
|
|
func DeleteDomain(c *gin.Context) {
|
|
cosy.Core[model.DnsDomain](c).Destroy()
|
|
}
|
|
|
|
func ListRecords(c *gin.Context) {
|
|
domainID := cast.ToUint64(c.Param("id"))
|
|
var params recordListQuery
|
|
_ = c.ShouldBindQuery(¶ms)
|
|
|
|
svc := dnsService.NewService()
|
|
records, err := svc.ListRecords(
|
|
c.Request.Context(),
|
|
domainID,
|
|
dnsService.RecordListOptions{
|
|
Filter: dnsService.RecordFilter{
|
|
Type: params.Type,
|
|
Name: params.Name,
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
page := lo.If(params.Page < 1, 1).Else(params.Page)
|
|
perPage := lo.If(params.PerPage <= 0, 50).Else(params.PerPage)
|
|
|
|
total := len(records)
|
|
start := max((page-1)*perPage, 0)
|
|
end := min(start+perPage, total)
|
|
|
|
var pagedRecords []dnsService.Record
|
|
if total == 0 || start >= total {
|
|
pagedRecords = []dnsService.Record{}
|
|
} else {
|
|
pagedRecords = records[start:end]
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": pagedRecords,
|
|
"pagination": buildPagination(page, perPage, int64(total)),
|
|
})
|
|
}
|
|
|
|
func CreateRecord(c *gin.Context) {
|
|
domainID := cast.ToUint64(c.Param("id"))
|
|
var payload recordRequest
|
|
if !cosy.BindAndValid(c, &payload) {
|
|
return
|
|
}
|
|
|
|
svc := dnsService.NewService()
|
|
record, err := svc.CreateRecord(c.Request.Context(), domainID, toRecordInput(payload))
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, record)
|
|
}
|
|
|
|
func UpdateRecord(c *gin.Context) {
|
|
domainID := cast.ToUint64(c.Param("id"))
|
|
recordID := c.Param("record_id")
|
|
|
|
var payload recordRequest
|
|
if !cosy.BindAndValid(c, &payload) {
|
|
return
|
|
}
|
|
|
|
svc := dnsService.NewService()
|
|
record, err := svc.UpdateRecord(c.Request.Context(), domainID, recordID, toRecordInput(payload))
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, record)
|
|
}
|
|
|
|
func DeleteRecord(c *gin.Context) {
|
|
domainID := cast.ToUint64(c.Param("id"))
|
|
recordID := c.Param("record_id")
|
|
|
|
svc := dnsService.NewService()
|
|
if err := svc.DeleteRecord(c.Request.Context(), domainID, recordID); err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// GetDDNSConfig returns the DDNS configuration for a domain.
|
|
func GetDDNSConfig(c *gin.Context) {
|
|
domainID := cast.ToUint64(c.Param("id"))
|
|
svc := dnsService.NewService()
|
|
|
|
cfg, err := svc.GetDDNSConfig(c.Request.Context(), domainID)
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, toDDNSResponse(cfg))
|
|
}
|
|
|
|
// ListDDNSConfig returns DDNS overview for all domains.
|
|
func ListDDNSConfig(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
domains, err := dnsService.ListDDNSDomains(ctx)
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
items := make([]ddnsDomainItem, 0, len(domains))
|
|
for _, domain := range domains {
|
|
cfg := domain.DDNSConfig
|
|
if cfg == nil {
|
|
cfg = &model.DDNSConfig{
|
|
Enabled: false,
|
|
IntervalSeconds: dnsService.DefaultDDNSInterval(),
|
|
Targets: []model.DDNSRecordTarget{},
|
|
}
|
|
} else if cfg.IntervalSeconds <= 0 {
|
|
cfg.IntervalSeconds = dnsService.DefaultDDNSInterval()
|
|
}
|
|
|
|
credName := ""
|
|
credProvider := ""
|
|
if domain.DnsCredential != nil {
|
|
credName = domain.DnsCredential.Name
|
|
credProvider = domain.DnsCredential.Provider
|
|
}
|
|
|
|
item := ddnsDomainItem{
|
|
ID: domain.ID,
|
|
Domain: domain.Domain,
|
|
CredentialName: credName,
|
|
CredentialProvider: credProvider,
|
|
Config: toDDNSResponse(cfg),
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": items,
|
|
})
|
|
}
|
|
|
|
// UpdateDDNSConfig updates DDNS settings for a domain and restarts its schedule.
|
|
func UpdateDDNSConfig(c *gin.Context) {
|
|
domainID := cast.ToUint64(c.Param("id"))
|
|
|
|
var payload ddnsConfigRequest
|
|
if !cosy.BindAndValid(c, &payload) {
|
|
return
|
|
}
|
|
|
|
if payload.Enabled && len(payload.RecordIDs) == 0 {
|
|
cosy.ErrHandler(c, dnsService.ErrDDNSTargetRequired)
|
|
return
|
|
}
|
|
|
|
svc := dnsService.NewService()
|
|
cfg, err := svc.UpdateDDNSConfig(c.Request.Context(), domainID, dnsService.DDNSUpdateInput{
|
|
Enabled: payload.Enabled,
|
|
IntervalSeconds: payload.IntervalSeconds,
|
|
RecordIDs: payload.RecordIDs,
|
|
})
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
if cfg.Enabled {
|
|
if err := cron.AddOrUpdateDDNSJob(domainID, cfg.IntervalSeconds); err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
} else {
|
|
if err := cron.RemoveDDNSJob(domainID); err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, toDDNSResponse(cfg))
|
|
}
|
|
|
|
// DeleteDDNSConfig removes DDNS settings for a domain and stops its schedule.
|
|
func DeleteDDNSConfig(c *gin.Context) {
|
|
domainID := cast.ToUint64(c.Param("id"))
|
|
|
|
svc := dnsService.NewService()
|
|
if err := svc.DeleteDDNSConfig(c.Request.Context(), domainID); err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
if err := cron.RemoveDDNSJob(domainID); err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
func buildPagination(page, perPage int, total int64) model.Pagination {
|
|
page = lo.If(page < 1, 1).Else(page)
|
|
perPage = lo.If(perPage <= 0, 50).Else(perPage)
|
|
|
|
totalPages := total / cast.ToInt64(perPage)
|
|
if total%cast.ToInt64(perPage) != 0 {
|
|
totalPages++
|
|
}
|
|
|
|
return model.Pagination{
|
|
Total: total,
|
|
PerPage: perPage,
|
|
CurrentPage: page,
|
|
TotalPages: totalPages,
|
|
}
|
|
}
|
|
|
|
func domainMutationHook(svc *dnsService.Service, isUpdate bool) func(ctx *cosy.Ctx[model.DnsDomain]) {
|
|
return func(ctx *cosy.Ctx[model.DnsDomain]) {
|
|
normalized, err := dnsService.NormalizeDomain(ctx.Model.Domain)
|
|
if err != nil {
|
|
ctx.AbortWithError(err)
|
|
return
|
|
}
|
|
|
|
credential, err := dnsService.LoadCredential(ctx.Request.Context(), ctx.Model.DnsCredentialID)
|
|
if err != nil {
|
|
ctx.AbortWithError(err)
|
|
return
|
|
}
|
|
|
|
excludeID := uint64(0)
|
|
if isUpdate {
|
|
if ctx.ID == 0 {
|
|
ctx.ID = ctx.GetParamID()
|
|
}
|
|
excludeID = ctx.ID
|
|
}
|
|
|
|
if err := dnsService.EnsureDomainUnique(ctx.Request.Context(), normalized, credential.ID, excludeID); err != nil {
|
|
ctx.AbortWithError(err)
|
|
return
|
|
}
|
|
|
|
ctx.Model.Domain = normalized
|
|
ctx.Model.Description = strings.TrimSpace(ctx.Model.Description)
|
|
ctx.Model.DnsCredentialID = credential.ID
|
|
}
|
|
}
|