diff --git a/.drone.yml b/.drone.yml index adec539..9c714b6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -58,8 +58,9 @@ steps: commands: - apk add --no-cache git - sed -i '/npm run build/d' ui/assets.go + - go install github.com/swaggo/swag/cmd/swag@latest - go generate -v ./... - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain environment: CGO_ENABLED: 0 @@ -73,8 +74,9 @@ steps: commands: - apk add --no-cache git - sed -i '/npm run build/d' ui/assets.go + - go install github.com/swaggo/swag/cmd/swag@latest - go generate -v ./... - - go build -v -tags netgo -ldflags '-w -X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain environment: CGO_ENABLED: 0 @@ -129,7 +131,7 @@ steps: image: golang:1-alpine commands: - apk add --no-cache git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} environment: CGO_ENABLED: 0 GOOS: darwin @@ -143,7 +145,7 @@ steps: image: golang:1-alpine commands: - apk add --no-cache git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} environment: CGO_ENABLED: 0 GOOS: darwin @@ -227,8 +229,9 @@ steps: commands: - apk add --no-cache git - sed -i '/npm run build/d' ui/assets.go + - go install github.com/swaggo/swag/cmd/swag@latest - go generate -v ./... - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain environment: CGO_ENABLED: 0 @@ -242,8 +245,9 @@ steps: commands: - apk add --no-cache git - sed -i '/npm run build/d' ui/assets.go + - go install github.com/swaggo/swag/cmd/swag@latest - go generate -v ./... - - go build -v -tags netgo -ldflags '-w -X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} - ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} happydomain environment: CGO_ENABLED: 0 @@ -290,7 +294,7 @@ steps: image: golang:1-alpine commands: - apk add --no-cache git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} environment: CGO_ENABLED: 0 GOOS: darwin @@ -304,7 +308,7 @@ steps: image: golang:1-alpine commands: - apk add --no-cache git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-darwin-${DRONE_STAGE_ARCH} environment: CGO_ENABLED: 0 GOOS: darwin @@ -388,8 +392,9 @@ steps: commands: - apk --no-cache add build-base git - sed -i '/npm run build/d' ui/assets.go + - go install github.com/swaggo/swag/cmd/swag@latest - go generate -v ./... - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el environment: CGO_ENABLED: 0 GOARM: 5 @@ -406,8 +411,9 @@ steps: commands: - apk --no-cache add build-base git - sed -i '/npm run build/d' ui/assets.go + - go install github.com/swaggo/swag/cmd/swag@latest - go generate -v ./... - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}el environment: CGO_ENABLED: 0 GOARM: 5 @@ -454,7 +460,7 @@ steps: image: golang:1-alpine commands: - apk --no-cache add build-base git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf environment: CGO_ENABLED: 0 GOARM: 6 @@ -470,7 +476,7 @@ steps: image: golang:1-alpine commands: - apk --no-cache add build-base git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}hf environment: CGO_ENABLED: 0 GOARM: 6 @@ -517,7 +523,7 @@ steps: image: golang:1-alpine commands: - apk --no-cache add build-base git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_BRANCH}-${DRONE_COMMIT}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 - ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 happydomain environment: CGO_ENABLED: 0 @@ -531,7 +537,7 @@ steps: image: golang:1-alpine commands: - apk --no-cache add build-base git - - go build -v -tags netgo -ldflags '-w -X main.version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 + - go build -v -tags netgo -ldflags '-w -X main.Version="${DRONE_TAG##v}" -X main.build=${DRONE_BUILD_NUMBER}' -o happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 - ln happydomain-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}v7 happydomain environment: CGO_ENABLED: 0 diff --git a/Dockerfile b/Dockerfile index 2e8bc8f..10005ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,9 +30,9 @@ COPY storage ./storage COPY utils ./utils COPY generate.go go.mod go.sum main.go ./ -RUN sed -i '/yarn --offline build/d' ui/assets.go && \ - go get -d -v && \ - go generate -v && \ +RUN sed -i '/npm run build/d' ui/assets.go && \ + go install github.com/swaggo/swag/cmd/swag@latest && \ + go generate -v ./... && \ go build -v -ldflags '-w' diff --git a/api/domains.go b/api/domains.go index d1e6bd7..6aac954 100644 --- a/api/domains.go +++ b/api/domains.go @@ -58,6 +58,19 @@ func declareDomainsRoutes(cfg *config.Options, router *gin.RouterGroup) { declareZonesRoutes(cfg, apiDomainsRoutes) } +// GetDomains retrieves all domains belonging to the user. +// +// @Summary Retrieve user's domains +// @Schemes +// @Description Retrieve all domains belonging to the user. +// @Tags domains +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Success 200 {array} happydns.Domain +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Unable to retrieve user's domains" +// @Router /domains [get] func GetDomains(c *gin.Context) { user := myUser(c) if user == nil { @@ -75,6 +88,21 @@ func GetDomains(c *gin.Context) { } } +// addDomain appends a new domain to those managed. +// +// @Summary Manage a new domain +// @Schemes +// @Description Append a new domain to those managed. +// @Tags domains +// @Accept json +// @Produce json +// @Param body body happydns.DomainMinimal true "Domain object that you want to manage through happyDomain." +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.Domain +// @Failure 400 {object} happydns.Error "Error in received data" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 500 {object} happydns.Error "Unable to retrieve current user's domains" +// @Router /domains [post] func addDomain(c *gin.Context) { var uz happydns.Domain err := c.ShouldBindJSON(&uz) @@ -140,15 +168,15 @@ func DomainHandler(c *gin.Context) { return } - // If source is provided, check that the domain is a parent of the source - var source *happydns.SourceMeta - if src, exists := c.Get("source"); exists { - source = &src.(*happydns.SourceCombined).SourceMeta - } else if src, exists := c.Get("sourcemeta"); exists { - source = src.(*happydns.SourceMeta) + // If provider is provided, check that the domain is a parent of the provider + var provider *happydns.ProviderMeta + if src, exists := c.Get("provider"); exists { + provider = &src.(*happydns.ProviderCombined).ProviderMeta + } else if src, exists := c.Get("providermeta"); exists { + provider = src.(*happydns.ProviderMeta) } - if source != nil && !source.Id.Equals(domain.IdProvider) { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Domain not found (not child of source)"}) + if provider != nil && !provider.Id.Equals(domain.IdProvider) { + c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Domain not found (not child of provider)"}) return } @@ -158,14 +186,40 @@ func DomainHandler(c *gin.Context) { } type apiDomain struct { - Id happydns.Identifier `json:"id"` - IdUser happydns.Identifier `json:"id_owner"` - IdProvider happydns.Identifier `json:"id_provider"` - DomainName string `json:"domain"` + // Id is the Domain's identifier in the database. + Id happydns.Identifier `json:"id" swaggertype:"string"` + + // IdUser is the identifier of the Domain's Owner. + IdUser happydns.Identifier `json:"id_owner" swaggertype:"string"` + + // IsProvider is the identifier of the Provider used to access and edit the + // Domain. + IdProvider happydns.Identifier `json:"id_provider" swaggertype:"string"` + + // DomainName is the FQDN of the managed Domain. + DomainName string `json:"domain"` + // Group is a hint string aims to group domains. + Group string `json:"group,omitempty"` + + // ZoneHistory are the metadata associated to each Zone saved with the + // current Domain. ZoneHistory []happydns.ZoneMeta `json:"zone_history"` - Group string `json:"group,omitempty"` } +// GetDomain retrieves information about a given Domain owned by the user. +// +// @Summary Retrieve Domain local information. +// @Schemes +// @Description Retrieve information in the database about a given Domain owned by the user. +// @Tags domains +// @Accept json +// @Produce json +// @Param domainId path string true "Domain identifier" +// @Security securitydefinitions.basic +// @Success 200 {object} apiDomain +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain not found" +// @Router /domains/{domainId} [get] func GetDomain(c *gin.Context) { domain := c.MustGet("domain").(*happydns.Domain) ret := &apiDomain{ @@ -190,6 +244,24 @@ func GetDomain(c *gin.Context) { c.JSON(http.StatusOK, ret) } +// UpdateDomain updates the information about a given Domain owned by the user. +// +// @Summary Update Domain local information. +// @Schemes +// @Description Updates the information about a given Domain owned by the user. +// @Tags domains +// @Accept json +// @Produce json +// @Param domainId path string true "Domain identifier" +// @Param body body happydns.Domain true "The new object overriding the current domain" +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.Domain +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 400 {object} happydns.Error "Identifier changed" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain not found" +// @Failure 500 {object} happydns.Error "Database writing error" +// @Router /domains/{domainId} [put] func UpdateDomain(c *gin.Context) { old := c.MustGet("domain").(*happydns.Domain) @@ -217,6 +289,22 @@ func UpdateDomain(c *gin.Context) { c.JSON(http.StatusOK, old) } +// delDomain removes a domain from the database. +// +// @Summary Stop managing a Domain. +// @Schemes +// @Description Delete all the information in the database about the given Domain. This only stops happyDomain from managing the Domain, it doesn't do anything on the Provider. +// @Tags domains +// @Accept json +// @Produce json +// @Param domainId path string true "Domain identifier" +// @Security securitydefinitions.basic +// @Success 204 "Domain deleted" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain not found" +// @Failure 500 {object} happydns.Error "Database writing error" +// @Router /domains/{domainId} [delete] func delDomain(c *gin.Context) { if err := storage.MainStore.DeleteDomain(c.MustGet("domain").(*happydns.Domain)); err != nil { log.Printf("%s was unable to DeleteDomain: %s", c.ClientIP(), err.Error()) @@ -224,5 +312,5 @@ func delDomain(c *gin.Context) { return } - c.JSON(http.StatusNoContent, true) + c.Status(http.StatusNoContent) } diff --git a/api/form.go b/api/form.go index 0391d33..837540d 100644 --- a/api/form.go +++ b/api/form.go @@ -40,10 +40,17 @@ import ( ) type FormState struct { - Id *happydns.Identifier `json:"_id,omitempty"` - Name string `json:"_comment"` - State int32 `json:"state"` - Recall string `json:"recall,omitempty"` + // Id for an already existing element. + Id *happydns.Identifier `json:"_id,omitempty" swaggertype:"string"` + + // User defined name of the element. + Name string `json:"_comment"` + + // State is the desired form to shows next (starting at 0). + State int32 `json:"state"` + + // Recall is the identifier for a saved FormState you want to retrieve. + Recall string `json:"recall,omitempty"` } func formDoState(cfg *config.Options, c *gin.Context, fs *FormState, data interface{}, defaultForm func(interface{}) *forms.CustomForm) (form *forms.CustomForm, d map[string]interface{}, err error) { diff --git a/api/provider_settings.go b/api/provider_settings.go index b03764c..f7fc7a0 100644 --- a/api/provider_settings.go +++ b/api/provider_settings.go @@ -54,15 +54,31 @@ func declareProviderSettingsRoutes(cfg *config.Options, router *gin.RouterGroup) type ProviderSettingsState struct { FormState - happydns.Provider + happydns.Provider `json:"Provider" swaggertype:"object"` } type ProviderSettingsResponse struct { - Provider *happydns.Provider `json:"Provider,omitempty"` + Provider *happydns.Provider `json:"Provider,omitempty" swaggertype:"object"` Values map[string]interface{} `json:"values,omitempty"` Form *forms.CustomForm `json:"form,omitempty"` } +// getProviderSettingsState creates or updates a Provider with human fillable forms. +// @Summary Assistant to Provider creation. +// @Schemes +// @Description This creates or updates a Provider with human fillable forms. +// @Tags provider_specs +// @Accept json +// @Produce json +// @Param providerType path string true "The provider's type" +// @Param body body ProviderSettingsState true "The current state of the Provider's settings, possibly empty (but not null)" +// @Security securitydefinitions.basic +// @Success 200 {object} ProviderSettingsResponse "The settings need more rafinement" +// @Success 200 {object} happydns.ProviderCombined "The Provider has been created with the given settings" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Provider not found" +// @Router /providers/_specs/{providerType}/settings [post] func getProviderSettingsState(cfg *config.Options, c *gin.Context) { user := myUser(c) if user == nil { diff --git a/api/provider_specs.go b/api/provider_specs.go index 8bd6b36..e81b84a 100644 --- a/api/provider_specs.go +++ b/api/provider_specs.go @@ -54,6 +54,15 @@ func declareProviderSpecsRoutes(router *gin.RouterGroup) { apiProviderSpecsRoutes.GET("", getProviderSpec) } +// listProviders returns the static list of usable providers in this happyDomain release. +// @Summary List all providers with which you can connect. +// @Schemes +// @Description This returns the static list of usable providers in this happyDomain release. +// @Tags provider_specs +// @Accept json +// @Produce json +// @Success 200 {object} map[string]providers.ProviderInfos{} "The list" +// @Router /providers/_specs [get] func listProviders(c *gin.Context) { srcs := providers.GetProviders() @@ -65,6 +74,17 @@ func listProviders(c *gin.Context) { c.JSON(http.StatusOK, ret) } +// getProviderSpecIcon returns the icon as image/png. +// @Summary Get the PNG icon. +// @Schemes +// @Description Return the icon as a image/png file for the given provider type. +// @Tags provider_specs +// @Accept json +// @Produce png +// @Param providerType path string true "The provider's type" +// @Success 200 {file} png +// @Failure 404 {object} happydns.Error "Provider type does not exist" +// @Router /providers/_specs/{providerType}/icon.png [get] func getProviderSpecIcon(c *gin.Context) { psid := string(c.Param("psid")) @@ -91,10 +111,24 @@ func ProviderSpecsHandler(c *gin.Context) { } type viewProviderSpec struct { - Fields []*forms.Field `json:"fields,omitempty"` - Capabilities []string `json:"capabilities,omitempty"` + // Fields describes the settings needed to configure the provider. + Fields []*forms.Field `json:"fields,omitempty"` + + // Capabilities exposes what the provider can do. + Capabilities []string `json:"capabilities,omitempty"` } +// getProviderSpec returns a description of the expected settings and the provider capabilities. +// @Summary Get the provider capabilities and expected settings. +// @Schemes +// @Description Return a description of the expected settings and the provider capabilities. +// @Tags provider_specs +// @Accept json +// @Produce json +// @Param providerType path string true "The provider's type" +// @Success 200 {object} viewProviderSpec +// @Failure 404 {object} happydns.Error "Provider type does not exist" +// @Router /providers/_specs/{providerType} [get] func getProviderSpec(c *gin.Context) { src := c.MustGet("providertype").(happydns.Provider) diff --git a/api/providers.go b/api/providers.go index 312608c..4dcb529 100644 --- a/api/providers.go +++ b/api/providers.go @@ -65,6 +65,18 @@ func declareProvidersRoutes(cfg *config.Options, router *gin.RouterGroup) { apiProviderRoutes.GET("/domains", getDomainsHostedByProvider) } +// getDomains retrieves all providers belonging to the user. +// @Summary Retrieve user's providers +// @Schemes +// @Description Retrieve all DNS providers belonging to the user. +// @Tags providers +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Success 200 {array} happydns.Provider +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Unable to retrieve user's domains" +// @Router /providers [get] func getProviders(c *gin.Context) { user := c.MustGet("LoggedUser").(*happydns.User) @@ -171,12 +183,39 @@ func ProviderHandler(c *gin.Context) { c.Next() } +// GetProvider retrieves information about a given Provider owned by the user. +// @Summary Retrieve Provider information. +// @Schemes +// @Description Retrieve information in the database about a given Provider owned by the user. +// @Tags providers +// @Accept json +// @Produce json +// @Param providerId path string true "Provider identifier" +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.ProviderCombined +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Provider not found" +// @Router /providers/{providerId} [get] func GetProvider(c *gin.Context) { provider := c.MustGet("provider").(*happydns.ProviderCombined) c.JSON(http.StatusOK, provider) } +// addProvider appends a new provider. +// @Summary Add a new provider +// @Schemes +// @Description Append a new provider for the user. +// @Tags providers +// @Accept json +// @Produce json +// @Param body body happydns.ProviderMinimal true "Provider to add" +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.Provider +// @Failure 400 {object} happydns.Error "Error in received data" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 500 {object} happydns.Error "Unable to retrieve current user's providers" +// @Router /providers [post] func addProvider(c *gin.Context) { user := c.MustGet("LoggedUser").(*happydns.User) @@ -196,6 +235,23 @@ func addProvider(c *gin.Context) { c.JSON(http.StatusOK, s) } +// UpdateProvider updates the information about a given Provider owned by the user. +// @Summary Update Provider information. +// @Schemes +// @Description Updates the information about a given Provider owned by the user. +// @Tags providers +// @Accept json +// @Produce json +// @Param providerId path string true "Provider identifier" +// @Param body body happydns.Provider true "The new object overriding the current provider" +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.Provider +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 400 {object} happydns.Error "Identifier changed" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Provider not found" +// @Failure 500 {object} happydns.Error "Database writing error" +// @Router /providers/{providerId} [put] func UpdateProvider(c *gin.Context) { provider := c.MustGet("provider").(*happydns.ProviderCombined) @@ -217,6 +273,21 @@ func UpdateProvider(c *gin.Context) { c.JSON(http.StatusOK, src) } +// deleteProvider removes a provider from the database. +// @Summary Delete a Provider. +// @Schemes +// @Description Delete a Provider from the database. It is required that no Domain are still managed by this Provider before calling this route. +// @Tags providers +// @Accept json +// @Produce json +// @Param providerId path string true "Provider identifier" +// @Security securitydefinitions.basic +// @Success 204 "Provider deleted" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Provider not found" +// @Failure 500 {object} happydns.Error "Database writing error" +// @Router /providers/{providerId} [delete] func deleteProvider(c *gin.Context) { user := c.MustGet("LoggedUser").(*happydns.User) providermeta := c.MustGet("providermeta").(*happydns.ProviderMeta) @@ -245,6 +316,22 @@ func deleteProvider(c *gin.Context) { c.JSON(http.StatusNoContent, nil) } +// getDomainsHostedByProvider lists domains available to management from the given Provider. +// @Summary Lists manageable domains from the Provider. +// @Schemes +// @Description List domains available from the given Provider. +// @Tags providers +// @Accept json +// @Produce json +// @Param providerId path string true "Provider identifier" +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.ProviderCombined +// @Failure 400 {object} happydns.Error "Unable to instantiate the provider" +// @Failure 400 {object} happydns.Error "The provider doesn't support domain listing" +// @Failure 400 {object} happydns.Error "Provider error" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Provider not found" +// @Router /providers/{providerId}/domains [get] func getDomainsHostedByProvider(c *gin.Context) { provider := c.MustGet("provider").(*happydns.ProviderCombined) diff --git a/api/resolver.go b/api/resolver.go index 66e683f..73e848d 100644 --- a/api/resolver.go +++ b/api/resolver.go @@ -51,11 +51,19 @@ func declareResolverRoutes(router *gin.RouterGroup) { router.POST("/resolver", runResolver) } +// resolverRequest holds the resolution parameters type resolverRequest struct { - Resolver string `json:"resolver"` - Custom string `json:"custom,omitempty"` + // Resolver is the name of the resolver to use (or local or custom). + Resolver string `json:"resolver"` + + // Custom is the address to the recursive server to use. + Custom string `json:"custom,omitempty"` + + // DomainName is the FQDN to resolve. DomainName string `json:"domain"` - Type string `json:"type"` + + // Type is the type of record to retrieve. + Type string `json:"type"` } func resolverANYQuestion(client dns.Client, resolver string, dn string) (r *dns.Msg, err error) { @@ -105,6 +113,49 @@ func resolverQuestion(client dns.Client, resolver string, dn string, rrType uint return r, err } +// DNSMsg is the documentation structur corresponding to dns.Msg +type DNSMsg struct { + // Question is the Question section of the DNS response. + Question []DNSQuestion + + // Answer is the list of Answer records in the DNS response. + Answer []interface{} `swaggertype:"object"` + + // Ns is the list of Authoritative records in the DNS response. + Ns []interface{} `swaggertype:"object"` + + // Extra is the list of extra records in the DNS response. + Extra []interface{} `swaggertype:"object"` +} + +type DNSQuestion struct { + // Name is the domain name researched. + Name string + + // Qtype is the type of record researched. + Qtype uint16 + + // Qclass is the class of record researched. + Qclass uint16 +} + +// runResolver performs a NS resolution for a given domain, with options. +// @Summary Perform a DNS resolution. +// @Schemes +// @Description Perform a NS resolution for a given domain, with options. +// @Tags resolver +// @Accept json +// @Produce json +// @Param body body resolverRequest true "Options to the resolution" +// @Success 200 {object} DNSMsg +// @Success 204 {object} happydns.Error "No content" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 403 {object} happydns.Error "The resolver refused to treat our request" +// @Failure 404 {object} happydns.Error "The domain doesn't exist" +// @Failure 406 {object} happydns.Error "The resolver returned an error" +// @Failure 500 {object} happydns.Error +// @Router /resolver [post] func runResolver(c *gin.Context) { var urr resolverRequest if err := c.ShouldBindJSON(&urr); err != nil { diff --git a/api/routes.go b/api/routes.go index 88f9a01..5051288 100644 --- a/api/routes.go +++ b/api/routes.go @@ -32,11 +32,38 @@ package api import ( + "fmt" + "net/http" + "strings" + "github.com/gin-gonic/gin" + swaggerfiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" "git.happydns.org/happydomain/config" + docs "git.happydns.org/happydomain/docs" ) +// @title happyDomain API +// @version 0.1 +// @description Finally a simple interface for domain names. + +// @contact.name happyDomain team +// @contact.email contact+api@happydomain.org + +// @license.name CeCILL Free Software License Agreement +// @license.url https://spdx.org/licenses/CECILL-2.1.html + +// @host localhost:8081 +// @BasePath /api + +// @securityDefinitions.basic BasicAuth + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +// @description Description for what is this security definition being used + func DeclareRoutes(cfg *config.Options, router *gin.Engine) { apiRoutes := router.Group("/api") @@ -54,4 +81,17 @@ func DeclareRoutes(cfg *config.Options, router *gin.Engine) { declareProvidersRoutes(cfg, apiAuthRoutes) declareProviderSettingsRoutes(cfg, apiAuthRoutes) declareUsersAuthRoutes(cfg, apiAuthRoutes) + + // Expose Swagger + if cfg.ExternalURL != "" { + docs.SwaggerInfo.Host = cfg.ExternalURL[strings.Index(cfg.ExternalURL, "://")+3:] + } else { + docs.SwaggerInfo.Host = fmt.Sprintf("localhost%s", cfg.Bind[strings.Index(cfg.Bind, ":"):]) + } + docs.SwaggerInfo.BasePath = "/api" + if cfg.BaseURL != "" { + docs.SwaggerInfo.BasePath = cfg.BaseURL + docs.SwaggerInfo.BasePath + } + router.GET("/swagger", func(c *gin.Context) { c.Redirect(http.StatusFound, "./swagger/index.html") }) + router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) } diff --git a/api/service_settings.go b/api/service_settings.go index 59f14e3..ddb3c75 100644 --- a/api/service_settings.go +++ b/api/service_settings.go @@ -53,13 +53,31 @@ func declareServiceSettingsRoutes(cfg *config.Options, router *gin.RouterGroup) type ServiceSettingsState struct { FormState - happydns.Service + happydns.Service `json:"Service" swaggertype:"object"` } type ServiceSettingsResponse struct { Services map[string][]*happydns.ServiceCombined `json:"services,omitempty"` + Values map[string]interface{} `json:"values,omitempty"` + Form *forms.CustomForm `json:"form,omitempty"` } +// getServiceSettingsState creates or updates a Service with human fillable forms. +// @Summary Assistant to Service creation. +// @Schemes +// @Description This creates or updates a Service with human fillable forms. +// @Tags service_specs +// @Accept json +// @Produce json +// @Param serviceType path string true "The service's type" +// @Param body body ServiceSettingsState true "The current state of the Service's parameters, possibly empty (but not null)" +// @Security securitydefinitions.basic +// @Success 200 {object} ServiceSettingsResponse "The settings need more rafinement" +// @Success 200 {object} happydns.ServiceCombined "The Service has been created with the given settings" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Service not found" +// @Router /service/{serviceType} [post] func getServiceSettingsState(cfg *config.Options, c *gin.Context) { domain := c.MustGet("domain").(*happydns.Domain) zone := c.MustGet("zone").(*happydns.Zone) @@ -118,7 +136,7 @@ func getServiceSettingsState(cfg *config.Options, c *gin.Context) { return } - c.JSON(http.StatusOK, ProviderSettingsResponse{ + c.JSON(http.StatusOK, ServiceSettingsResponse{ Form: form, Values: p, }) diff --git a/api/service_specs.go b/api/service_specs.go index daa2ac3..932f6de 100644 --- a/api/service_specs.go +++ b/api/service_specs.go @@ -54,6 +54,15 @@ func declareServiceSpecsRoutes(router *gin.RouterGroup) { apiServiceSpecsRoutes.GET("", getServiceSpec) } +// getServiceSpecs returns the static list of usable services in this happyDomain release. +// @Summary List all services with which you can connect. +// @Schemes +// @Description This returns the static list of usable services in this happyDomain release. +// @Tags service_specs +// @Accept json +// @Produce json +// @Success 200 {object} map[string]svcs.ServiceInfos{} "The list" +// @Router /service_specs [get] func getServiceSpecs(c *gin.Context) { services := svcs.GetServices() @@ -65,6 +74,17 @@ func getServiceSpecs(c *gin.Context) { c.JSON(http.StatusOK, ret) } +// getServiceSpecIcon returns the icon as image/png. +// @Summary Get the PNG icon. +// @Schemes +// @Description Return the icon as a image/png file for the given service type. +// @Tags service_specs +// @Accept json +// @Produce png +// @Param serviceType path string true "The service's type" +// @Success 200 {file} png +// @Failure 404 {object} happydns.Error "Service type does not exist" +// @Router /service_specs/{serviceType}/icon.png [get] func getServiceSpecIcon(c *gin.Context) { ssid := string(c.Param("ssid")) @@ -144,6 +164,17 @@ func getSpecs(svcType reflect.Type) viewServiceSpec { return viewServiceSpec{fields} } +// getServiceSpec returns a description of the expected fields. +// @Summary Get the service expected fields. +// @Schemes +// @Description Return a description of the expected fields. +// @Tags service_specs +// @Accept json +// @Produce json +// @Param serviceType path string true "The service's type" +// @Success 200 {object} viewServiceSpec +// @Failure 404 {object} happydns.Error "Service type does not exist" +// @Router /services/_specs/{serviceType} [get] func getServiceSpec(c *gin.Context) { svctype := c.MustGet("servicetype").(reflect.Type) diff --git a/api/services.go b/api/services.go deleted file mode 100644 index a5762a4..0000000 --- a/api/services.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright or © or Copr. happyDNS (2020) -// -// contact@happydomain.org -// -// This software is a computer program whose purpose is to provide a modern -// interface to interact with DNS systems. -// -// This software is governed by the CeCILL license under French law and abiding -// by the rules of distribution of free software. You can use, modify and/or -// redistribute the software under the terms of the CeCILL license as -// circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, modify -// and redistribute granted by the license, users are provided only with a -// limited warranty and the software's author, the holder of the economic -// rights, and the successive licensors have only limited liability. -// -// In this respect, the user's attention is drawn to the risks associated with -// loading, using, modifying and/or developing or reproducing the software by -// the user in light of its specific status of free software, that may mean -// that it is complicated to manipulate, and that also therefore means that it -// is reserved for developers and experienced professionals having in-depth -// computer knowledge. Users are therefore encouraged to load and test the -// software's suitability as regards their requirements in conditions enabling -// the security of their systems and/or data to be ensured and, more generally, -// to use and operate it in the same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package api - -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - - "git.happydns.org/happydomain/model" - "git.happydns.org/happydomain/services" - "git.happydns.org/happydomain/storage" -) - -func declareServiceRoutes(router *gin.RouterGroup) { - router.GET("/services", listServices) - //router.POST("/services", newService) - - //router.POST("/domains/:domain/analyze", analyzeDomain) -} - -func listServices(c *gin.Context) { - ret := map[string]svcs.ServiceInfos{} - - for k, svc := range *svcs.GetServices() { - ret[k] = svc.Infos - } - - c.JSON(http.StatusOK, ret) -} - -func analyzeDomain(c *gin.Context) { - domain := c.MustGet("domain").(*happydns.Domain) - user := myUser(c) - if user == nil { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "User not defined"}) - return - } - - provider, err := storage.MainStore.GetProvider(user, domain.IdProvider) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get the related provider: %s", err.Error())}) - return - } - - zone, err := provider.ImportZone(domain) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to import zone: %s", err.Error())}) - return - } - - services, defaultTTL, err := svcs.AnalyzeZone(domain.DomainName, zone) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during analysis: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "services": services, - "defaultTTL": defaultTTL, - }) -} diff --git a/api/user_auth.go b/api/user_auth.go index 096b822..7d5cb2a 100644 --- a/api/user_auth.go +++ b/api/user_auth.go @@ -71,10 +71,17 @@ func declareAuthenticationRoutes(opts *config.Options, router *gin.RouterGroup) } type DisplayUser struct { - Id happydns.Identifier `json:"id"` - Email string `json:"email"` - CreatedAt time.Time `json:"created_at,omitempty"` - Settings happydns.UserSettings `json:"settings,omitempty"` + // Id is the user identifier + Id happydns.Identifier `json:"id" swaggertype:"string"` + + // Email is the user email. + Email string `json:"email"` + + // CreatedAt stores the date of the account creation. + CreatedAt time.Time `json:"created_at,omitempty"` + + // Settings holds the user configuration. + Settings happydns.UserSettings `json:"settings,omitempty"` } func currentUser(u *happydns.User) *DisplayUser { @@ -86,6 +93,18 @@ func currentUser(u *happydns.User) *DisplayUser { } } +// displayAuthToken returns the user information. +// +// @Summary User info. +// @Schemes +// @Description Retrieve information about the currently logged user. +// @Tags user_auth +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Success 200 {object} DisplayUser +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Router /auth [get] func displayAuthToken(c *gin.Context) { user := c.MustGet("LoggedUser").(*happydns.User) @@ -117,6 +136,17 @@ func displayNotAuthToken(opts *config.Options, c *gin.Context) { } } +// logout closes the user session. +// +// @Summary Close session. +// @Schemes +// @Description Erase the HTTP-only cookie. This leads to user logout in its browser. +// @Tags user_auth +// @Accept json +// @Produce json +// @Success 204 {null} null "Loged out" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Router /auth/logout [post] func logout(opts *config.Options, c *gin.Context) { c.SetCookie( COOKIE_NAME, @@ -127,14 +157,30 @@ func logout(opts *config.Options, c *gin.Context) { opts.DevProxy == "" && !strings.HasPrefix(opts.ExternalURL, "http://"), true, ) - c.JSON(http.StatusNoContent, true) + c.Status(http.StatusNoContent) } type loginForm struct { - Email string + // Email of the user. + Email string + + // Password of the user. Password string } +// checkAuth validate user authentication and delivers a session token. +// +// @Summary Authenticate user. +// @Schemes +// @Description Validate user authentication and delivers a session token. +// @Tags user_auth +// @Accept json +// @Produce json +// @Param body body loginForm true "Login information" +// @Success 200 {object} DisplayUser "Login success" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 500 {object} happydns.Error +// @Router /auth [post] func checkAuth(opts *config.Options, c *gin.Context) { var lf loginForm if err := c.ShouldBindJSON(&lf); err != nil { diff --git a/api/users.go b/api/users.go index a75ab51..aa5e635 100644 --- a/api/users.go +++ b/api/users.go @@ -93,16 +93,27 @@ func myUser(c *gin.Context) (user *happydns.User) { return } -type UploadedUser struct { - Kind string +type UserRegistration struct { Email string Password string Language string `json:"lang,omitempty"` Newsletter bool `json:"wantReceiveUpdate,omitempty"` } +// registerUser checks and appends a user in the database. +// @Summary Register account. +// @Schemes +// @Description Register a new happyDomain account (when using internal authentication system). +// @Tags users +// @Accept json +// @Produce json +// @Param body body UserRegistration true "Account information" +// @Success 200 {object} happydns.User "The created user" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 500 {object} happydns.Error +// @Router /users [post] func registerUser(opts *config.Options, c *gin.Context) { - var uu UploadedUser + var uu UserRegistration err := c.ShouldBindJSON(&uu) if err != nil { log.Printf("%s sends invalid User JSON: %s", c.ClientIP(), err.Error()) @@ -150,8 +161,27 @@ func registerUser(opts *config.Options, c *gin.Context) { c.JSON(http.StatusOK, user) } +type UserSpecialAction struct { + // Kind of special action to perform: "recovery" or "validation". + Kind string + + // Email on which to perform actions. + Email string +} + +// specialUserOperations performs account recovery. +// @Summary Account recovery. +// @Schemes +// @Description This will send an email to the user either to recover its account or with a new email validation link. +// @Tags users +// @Accept json +// @Produce json +// @Param body body UserSpecialAction true "Description of the action to perform and email of the user" +// @Success 200 {object} happydns.Error "Perhaps something happen" +// @Failure 500 {object} happydns.Error +// @Router /users [patch] func specialUserOperations(opts *config.Options, c *gin.Context) { - var uu UploadedUser + var uu UserSpecialAction err := c.ShouldBindJSON(&uu) if err != nil { log.Printf("%s sends invalid User JSON: %s", c.ClientIP(), err.Error()) @@ -229,12 +259,41 @@ func getUser(c *gin.Context) { c.JSON(http.StatusOK, user) } +// getUserSettings gets the settings of the given user. +// @Summary Retrieve user's settings. +// @Schemes +// @Description Retrieve the user's settings. +// @Tags users +// @Accept json +// @Produce json +// @Param userId path string true "User identifier" +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.UserSettings "User settings" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 403 {object} happydns.Error "Not your account" +// @Router /users/{userId}/settings [get] func getUserSettings(c *gin.Context) { user := c.MustGet("user").(*happydns.User) c.JSON(http.StatusOK, user.Settings) } +// changeUserSettings updates the settings of the given user. +// @Summary Update user's settings. +// @Schemes +// @Description Update the user's settings. +// @Tags users +// @Accept json +// @Produce json +// @Param userId path string true "User identifier" +// @Param body body happydns.UserSettings true "User settings" +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.UserSettings "User settings" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 403 {object} happydns.Error "Not your account" +// @Failure 500 {object} happydns.Error +// @Router /users/{userId}/settings [post] func changeUserSettings(c *gin.Context) { user := c.MustGet("user").(*happydns.User) @@ -262,6 +321,22 @@ type passwordForm struct { PasswordConfirm string } +// changePassword changes the password of the given account. +// @Summary Change password +// @Schemes +// @Description Change the password of the given account. +// @Tags users +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param userId path string true "User identifier" +// @Param body body passwordForm true "Password confirmation" +// @Success 204 {null} null +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 403 {object} happydns.Error "Bad current password" +// @Failure 500 {object} happydns.Error +// @Router /users/{userId}/new_password [post] func changePassword(opts *config.Options, c *gin.Context) { user := c.MustGet("authuser").(*happydns.UserAuth) @@ -314,6 +389,22 @@ func changePassword(opts *config.Options, c *gin.Context) { logout(opts, c) } +// deleteUser delete the account related to the given user. +// @Summary Drop account +// @Schemes +// @Description Delete the account related to the given user. +// @Tags users +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param userId path string true "User identifier" +// @Param body body passwordForm true "Password confirmation" +// @Success 204 {null} null +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 403 {object} happydns.Error "Bad current password" +// @Failure 500 {object} happydns.Error +// @Router /users/{userId}/delete [post] func deleteUser(opts *config.Options, c *gin.Context) { user := c.MustGet("authuser").(*happydns.UserAuth) @@ -408,9 +499,23 @@ func userAuthHandler(c *gin.Context) { } type UploadedAddressValidation struct { + // Key able to validate the email address. Key string } +// validateUserAddress validates a user address after registration. +// @Summary Validate e-mail address. +// @Schemes +// @Description This is the route called by the web interface in order to validate the e-mail address of the user. +// @Tags users +// @Accept json +// @Produce json +// @Param userId path string true "User identifier" +// @Param body body UploadedAddressValidation true "Validation form" +// @Success 204 {null} null "Email validated, you can now login" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 500 {object} happydns.Error +// @Router /users/{userId}/email [post] func validateUserAddress(c *gin.Context) { user := c.MustGet("authuser").(*happydns.UserAuth) @@ -434,14 +539,30 @@ func validateUserAddress(c *gin.Context) { return } - c.JSON(http.StatusNoContent, true) + c.Status(http.StatusNoContent) } type UploadedAccountRecovery struct { - Key string + // Key is the secret sent by email to the user. + Key string + + // Password is the new password to use with this account. Password string } +// recoverUserAccount performs account recovery by reseting the password of the account. +// @Summary Reset password with link in email. +// @Schemes +// @Description This performs account recovery by reseting the password of the account. +// @Tags users +// @Accept json +// @Produce json +// @Param userId path string true "User identifier" +// @Param body body UploadedAccountRecovery true "Recovery form" +// @Success 204 {null} null "Recovery completed, you can now login with your new credentials" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 500 {object} happydns.Error +// @Router /users/{userId}/recovery [post] func recoverUserAccount(c *gin.Context) { user := c.MustGet("authuser").(*happydns.UserAuth) @@ -475,19 +596,41 @@ func recoverUserAccount(c *gin.Context) { } log.Printf("%s: User recovered: %s", c.ClientIP(), user.Email) - c.JSON(http.StatusNoContent, true) + c.Status(http.StatusNoContent) } +// getSession gets the content of the current user's session. +// @Summary Retrieve user's session content +// @Schemes +// @Description Get the content of the current user's session. +// @Tags users +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Success 200 {object} happydns.Session +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Router /sessions [get] func getSession(c *gin.Context) { session := c.MustGet("MySession").(*happydns.Session) c.JSON(http.StatusOK, session) } +// clearSession removes the content of the current user's session. +// @Summary Remove user's session content +// @Schemes +// @Description Remove the content of the current user's session. +// @Tags users +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Success 204 {null} null +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Router /sessions [delete] func clearSession(c *gin.Context) { session := c.MustGet("MySession").(*happydns.Session) session.ClearSession() - c.JSON(http.StatusOK, true) + c.Status(http.StatusNoContent) } diff --git a/api/version.go b/api/version.go index 825d986..0419430 100644 --- a/api/version.go +++ b/api/version.go @@ -37,10 +37,27 @@ import ( "github.com/gin-gonic/gin" ) +var ( + HDVersion Version +) + func DeclareVersionRoutes(router *gin.RouterGroup) { router.GET("/version", showVersion) } -func showVersion(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"version": 0.1}) +type Version struct { + Version string `json:"version"` +} + +// showVersion returns the current happyDomain version. +// @Summary Get happyDomain version +// @Schemes +// @Description Retrieve the current happyDomain version. +// @Tags version +// @Accept json +// @Produce json +// @Success 200 {object} Version +// @Router /version [get] +func showVersion(c *gin.Context) { + c.JSON(http.StatusOK, HDVersion) } diff --git a/api/zones.go b/api/zones.go index d9124f5..3ce8903 100644 --- a/api/zones.go +++ b/api/zones.go @@ -125,13 +125,50 @@ func subdomainHandler(c *gin.Context) { c.Next() } +type zoneServices struct { + Services []*happydns.ServiceCombined `json:"services"` +} + +// getZoneSubdomain returns the services associated with a given subdomain. +// @Summary List services +// @Schemes +// @Description Returns the services associated with the given subdomain. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)" +// @Success 200 {object} zoneServices +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Router /domains/{domainId}/zone/{zoneId}/{subdomain} [get] func getZoneSubdomain(c *gin.Context) { zone := c.MustGet("zone").(*happydns.Zone) subdomain := c.MustGet("subdomain").(string) - c.JSON(http.StatusOK, gin.H{"services": zone.Services[subdomain]}) + c.JSON(http.StatusOK, zoneServices{ + Services: zone.Services[subdomain], + }) } +// addZoneService adds a Service to the given subdomain of the Zone. +// @Summary Add a Service. +// @Schemes +// @Description Add a Service to the given subdomain of the Zone. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)" +// @Success 200 {object} happydns.Zone +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services [post] func addZoneService(c *gin.Context) { domain := c.MustGet("domain").(*happydns.Domain) zone := c.MustGet("zone").(*happydns.Zone) @@ -178,6 +215,22 @@ func serviceIdHandler(c *gin.Context) { c.Next() } +// getServiceService retrieves the designated Service. +// @Summary Get the Service. +// @Schemes +// @Description Retrieve the designated Service. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)" +// @Param serviceId path string true "Service identifier" +// @Success 200 {object} happydns.ServiceCombined +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services/{serviceId} [get] func getZoneService(c *gin.Context) { zone := c.MustGet("zone").(*happydns.Zone) serviceid := c.MustGet("serviceid").([]byte) @@ -186,6 +239,19 @@ func getZoneService(c *gin.Context) { c.JSON(http.StatusOK, zone.FindSubdomainService(subdomain, serviceid)) } +// retrieveZone retrieves the current zone deployed on the NS Provider. +// @Summary Retrieve the zone on the Provider. +// @Schemes +// @Description Retrieve the current zone deployed on the NS Provider. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Success 200 {object} happydns.ZoneMeta "The new zone metadata" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain not found" +// @Router /domains/{domainId}/retrieve_zone [post] func retrieveZone(c *gin.Context) { user := c.MustGet("LoggedUser").(*happydns.User) domain := c.MustGet("domain").(*happydns.Domain) @@ -285,6 +351,24 @@ func importZone(c *gin.Context) { c.JSON(http.StatusOK, zone) } +// diffZones computes the difference between the two zone identifiers given. +// @Summary Compute differences between zones. +// @Schemes +// @Description Compute the difference between the two zone identifiers given. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId1 path string true "Zone identifier to use as the old one. Currently only @ are expected, to use the currently deployed zone." +// @Param zoneId2 path string true "Zone identifier to use as the new one" +// @Success 200 {object} []string "Differences, reported as text, one diff per item" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain not found" +// @Failure 500 {object} happydns.Error +// @Failure 501 {object} happydns.Error "Diff between to zone identifier, currently not supported" +// @Router /domains/{domainId}/diff_zones/{zoneId1}/{zoneId2} [post] func diffZones(c *gin.Context) { user := c.MustGet("LoggedUser").(*happydns.User) domain := c.MustGet("domain").(*happydns.Domain) @@ -331,6 +415,23 @@ func diffZones(c *gin.Context) { c.JSON(http.StatusOK, rrCorected) } +// applyZone performs the requested changes with the provider. +// @Summary Performs requested changes to the real zone. +// @Schemes +// @Description Perform the requested changes with the provider. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Param body body []string true "Differences (from /diff_zones) to apply" +// @Success 200 {object} happydns.ZoneMeta "The new Zone metadata containing the current zone" +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Failure 500 {object} happydns.Error +// @Router /domains/{domainId}/zone/{zoneId}/apply_changes [post] func applyZone(c *gin.Context) { user := c.MustGet("LoggedUser").(*happydns.User) domain := c.MustGet("domain").(*happydns.Domain) @@ -422,6 +523,20 @@ func applyZone(c *gin.Context) { c.JSON(http.StatusOK, newZone.ZoneMeta) } +// viewZone creates a flatten export of the zone. +// @Summary Get flatten zone file. +// @Schemes +// @Description Create a flatten export of the zone that can be read as a BIND-like file. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Success 200 {object} string "The exported zone file (with initial and leading JSON quote)" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Router /domains/{domainId}/zone/{zoneId}/view [post] func viewZone(c *gin.Context) { domain := c.MustGet("domain").(*happydns.Domain) zone := c.MustGet("zone").(*happydns.Zone) @@ -435,6 +550,21 @@ func viewZone(c *gin.Context) { c.JSON(http.StatusOK, ret) } +// UpdateZoneService adds or updates a service inside the given Zone. +// @Summary Add or update a Service. +// @Schemes +// @Description Add or update a Service inside the given Zone. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Param body body happydns.ServiceCombined true "Service to update" +// @Success 200 {object} happydns.Zone +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Router /domains/{domainId}/zone/{zoneId} [patch] func UpdateZoneService(c *gin.Context) { domain := c.MustGet("domain").(*happydns.Domain) zone := c.MustGet("zone").(*happydns.Zone) @@ -465,6 +595,23 @@ func UpdateZoneService(c *gin.Context) { c.JSON(http.StatusOK, zone) } +// deleteZoneService drops the given Service. +// @Summary Drop the given Service. +// @Schemes +// @Description Drop the given Service. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)" +// @Param serviceId path string true "Service identifier" +// @Success 200 {object} happydns.Zone +// @Failure 400 {object} happydns.Error "Invalid input" +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services/{serviceId} [delete] func deleteZoneService(c *gin.Context) { domain := c.MustGet("domain").(*happydns.Domain) zone := c.MustGet("zone").(*happydns.Zone) @@ -494,6 +641,22 @@ type serviceRecord struct { Fields *dns.RR `json:"fields,omitempty"` } +// getServiceRecords retrieves the records that will be generated by a Service. +// @Summary Get the records for a Service. +// @Schemes +// @Description Retrieve the records that will be generated by a Service. +// @Tags zones +// @Accept json +// @Produce json +// @Security securitydefinitions.basic +// @Param domainId path string true "Domain identifier" +// @Param zoneId path string true "Zone identifier" +// @Param subdomain path string true "Part of the subdomain considered for the service (@ for the root of the zone ; subdomain is relative to the root, do not include it)" +// @Param serviceId path string true "Service identifier" +// @Success 200 {object} happydns.Zone +// @Failure 401 {object} happydns.Error "Authentication failure" +// @Failure 404 {object} happydns.Error "Domain or Zone not found" +// @Router /domains/{domainId}/zone/{zoneId}/{subdomain}/services/{serviceId}/records [get] func getServiceRecords(c *gin.Context) { domain := c.MustGet("domain").(*happydns.Domain) zone := c.MustGet("zone").(*happydns.Zone) diff --git a/generate.go b/generate.go index ce72661..21b048f 100644 --- a/generate.go +++ b/generate.go @@ -33,3 +33,4 @@ package main //go:generate go run generators/gen_icon.go providers providers //go:generate go run generators/gen_icon.go services svcs +//go:generate swag init --generalInfo api/routes.go diff --git a/go.mod b/go.mod index d7e62f5..51a094a 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,10 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/G-Core/gcore-dns-sdk-go v0.2.6 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/goquery v1.8.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/aws/aws-sdk-go-v2 v1.18.1 // indirect @@ -57,6 +60,10 @@ require ( github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-gandi/go-gandi v0.6.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect @@ -76,11 +83,13 @@ require ( github.com/hexonet/go-sdk/v3 v3.5.4 // indirect github.com/jinzhu/copier v0.3.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -103,6 +112,9 @@ require ( github.com/sirupsen/logrus v1.9.0 // indirect github.com/softlayer/softlayer-go v1.1.2 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/swaggo/gin-swagger v1.6.0 // indirect + github.com/swaggo/swag v1.16.1 // indirect github.com/transip/gotransip/v6 v6.20.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect @@ -128,6 +140,7 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/mail.v2 v2.3.1 // indirect gopkg.in/ns1/ns1-go.v2 v2.7.4 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect moul.io/http2curl v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 9e540f2..6e42f0e 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,14 @@ github.com/G-Core/gcore-dns-sdk-go v0.2.3 h1:WODi+qWlZyF7E7SH8rq/DCACa/Zhsuhu1h0 github.com/G-Core/gcore-dns-sdk-go v0.2.3/go.mod h1:TM+VaDvBPObF+x085lS3i0kc2OPAkuW2c4Leg7Pe6jI= github.com/G-Core/gcore-dns-sdk-go v0.2.6 h1:R82ANd7BnhIe2mV/12Ebdx9QYVl2++E4kfDIu97JEOk= github.com/G-Core/gcore-dns-sdk-go v0.2.6/go.mod h1:KliUjfPonDvXyAGNiuO+MYVG/7lmWHZ+4Hi0sPxgOjg= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY= github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= @@ -152,6 +158,16 @@ github.com/go-gandi/go-gandi v0.6.0 h1:RgFoevggRRp7hF9XsOmWmtwbUg2axhe2ygEdd6Mts github.com/go-gandi/go-gandi v0.6.0/go.mod h1:9NoYyfWCjFosClPiWjkbbRK5UViaZ4ctpT8/pKSSFlw= github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM= github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -253,6 +269,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -264,6 +282,7 @@ github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8t github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -275,6 +294,10 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -371,6 +394,12 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= +github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/transip/gotransip/v6 v6.20.0 h1:AuvwyOZ51f2brzMbTqlRy/wmaM3kF7Vx5Wds8xcDflY= @@ -437,6 +466,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -473,6 +503,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -569,6 +600,7 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -591,7 +623,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 9c9815d..dc93dcd 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,7 @@ import ( "github.com/fatih/color" + "git.happydns.org/happydomain/api" "git.happydns.org/happydomain/config" "git.happydns.org/happydomain/internal/app" "git.happydns.org/happydomain/storage" @@ -58,6 +59,10 @@ var ( func main() { var err error + api.HDVersion = api.Version{ + Version: Version, + } + log.Println("This is happyDomain", Version) rand.Seed(time.Now().UTC().UnixNano()) diff --git a/model/domain.go b/model/domain.go index 901b039..a2ff0ea 100644 --- a/model/domain.go +++ b/model/domain.go @@ -35,17 +35,27 @@ import ( "github.com/miekg/dns" ) +// DomainMinimal is used for swagger documentation as Domain add. +type DomainMinimal struct { + // IsProvider is the identifier of the Provider used to access and edit the + // Domain. + IdProvider Identifier `json:"id_provider" swaggertype:"string"` + + // DomainName is the FQDN of the managed Domain. + DomainName string `json:"domain"` +} + // Domain holds information about a domain name own by a User. type Domain struct { // Id is the Domain's identifier in the database. - Id Identifier `json:"id"` + Id Identifier `json:"id" swaggertype:"string"` // IdUser is the identifier of the Domain's Owner. - IdUser Identifier `json:"id_owner"` + IdUser Identifier `json:"id_owner" swaggertype:"string"` // IsProvider is the identifier of the Provider used to access and edit the // Domain. - IdProvider Identifier `json:"id_provider"` + IdProvider Identifier `json:"id_provider" swaggertype:"string"` // DomainName is the FQDN of the managed Domain. DomainName string `json:"domain"` @@ -55,7 +65,7 @@ type Domain struct { // ZoneHistory are the identifiers to the Zone attached to the current // Domain. - ZoneHistory []Identifier `json:"zone_history"` + ZoneHistory []Identifier `json:"zone_history" swaggertype:"array,string"` } // Domains is an array of Domain. diff --git a/model/error.go b/model/error.go new file mode 100644 index 0000000..f6654f4 --- /dev/null +++ b/model/error.go @@ -0,0 +1,6 @@ +package happydns + +type Error struct { + // Err describe the error to display to the user. + Err string `json:"errmsg"` +} diff --git a/model/provider.go b/model/provider.go index 35b52b8..c9ee298 100644 --- a/model/provider.go +++ b/model/provider.go @@ -46,16 +46,27 @@ type Provider interface { DNSControlName() string } +// ProviderMinimal is used for swagger documentation as Provider add. +type ProviderMinimal struct { + // Type is the string representation of the Provider's type. + Type string `json:"_srctype"` + + Provider + + // Comment is a string that helps user to distinguish the Provider. + Comment string `json:"_comment,omitempty"` +} + // ProviderMeta holds the metadata associated to a Provider. type ProviderMeta struct { // Type is the string representation of the Provider's type. Type string `json:"_srctype"` // Id is the Provider's identifier. - Id Identifier `json:"_id"` + Id Identifier `json:"_id" swaggertype:"string"` // OwnerId is the User's identifier for the current Provider. - OwnerId Identifier `json:"_ownerid"` + OwnerId Identifier `json:"_ownerid" swaggertype:"string"` // Comment is a string that helps user to distinguish the Provider. Comment string `json:"_comment,omitempty"` diff --git a/model/service.go b/model/service.go index 30fdf15..a879d92 100644 --- a/model/service.go +++ b/model/service.go @@ -57,10 +57,10 @@ type ServiceMeta struct { Type string `json:"_svctype"` // Id is the Service's identifier. - Id Identifier `json:"_id,omitempty"` + Id Identifier `json:"_id,omitempty" swaggertype:"string"` // OwnerId is the User's identifier for the current Service. - OwnerId Identifier `json:"_ownerid,omitempty"` + OwnerId Identifier `json:"_ownerid,omitempty" swaggertype:"string"` // Domain contains the abstract domain where this Service relates. Domain string `json:"_domain"` diff --git a/model/session.go b/model/session.go index d93ffbc..94ef249 100644 --- a/model/session.go +++ b/model/session.go @@ -42,10 +42,10 @@ import ( // Session holds informatin about a User's currently connected. type Session struct { // Id is the Session's identifier. - Id Identifier `json:"id"` + Id Identifier `json:"id" swaggertype:"string"` // IdUser is the User's identifier of the Session. - IdUser Identifier `json:"login"` + IdUser Identifier `json:"login" swaggertype:"string"` // IssuedAt holds the creation date of the Session. IssuedAt time.Time `json:"time"` diff --git a/model/source.go b/model/source.go deleted file mode 100644 index a12f85f..0000000 --- a/model/source.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright or © or Copr. happyDNS (2020) -// -// contact@happydomain.org -// -// This software is a computer program whose purpose is to provide a modern -// interface to interact with DNS systems. -// -// This software is governed by the CeCILL license under French law and abiding -// by the rules of distribution of free software. You can use, modify and/or -// redistribute the software under the terms of the CeCILL license as -// circulated by CEA, CNRS and INRIA at the following URL -// "http://www.cecill.info". -// -// As a counterpart to the access to the source code and rights to copy, modify -// and redistribute granted by the license, users are provided only with a -// limited warranty and the software's author, the holder of the economic -// rights, and the successive licensors have only limited liability. -// -// In this respect, the user's attention is drawn to the risks associated with -// loading, using, modifying and/or developing or reproducing the software by -// the user in light of its specific status of free software, that may mean -// that it is complicated to manipulate, and that also therefore means that it -// is reserved for developers and experienced professionals having in-depth -// computer knowledge. Users are therefore encouraged to load and test the -// software's suitability as regards their requirements in conditions enabling -// the security of their systems and/or data to be ensured and, more generally, -// to use and operate it in the same conditions as regards security. -// -// The fact that you are presently reading this means that you have had -// knowledge of the CeCILL license and that you accept its terms. - -package happydns - -import ( - "github.com/miekg/dns" -) - -// Source is where Domains and Zones can be managed. -type Source interface { - // Validate tells if the Source's settings are good. - Validate() error - - // DomainExists tells if the given domain exists for the Source. - DomainExists(string) error - - // ImportZone retrieves all RRs for the given Domain. - ImportZone(*Domain) ([]dns.RR, error) - - // AddRR adds an RR in the zone of the given Domain. - AddRR(*Domain, dns.RR) error - - // DeleteRR removes the given RR in the zone of the given Domain. - DeleteRR(*Domain, dns.RR) error - - // UpdateSOA tries to update the Zone's SOA record, according to the - // given parameters. - UpdateSOA(*Domain, *dns.SOA, bool) error -} - -// SourceMeta holds the metadata associated to a Source. -type SourceMeta struct { - // Type is the string representation of the Source's type. - Type string `json:"_srctype"` - - // Id is the Source's identifier. - Id Identifier `json:"_id"` - - // OwnerId is the User's identifier for the current Source. - OwnerId Identifier `json:"_ownerid"` - - // Comment is a string that helps user to distinguish the Source. - Comment string `json:"_comment,omitempty"` -} - -// SourceCombined combined SourceMeta + Source -type SourceCombined struct { - Source - SourceMeta -} diff --git a/model/zone.go b/model/zone.go index bd2caec..0100fcf 100644 --- a/model/zone.go +++ b/model/zone.go @@ -42,10 +42,10 @@ import ( // ZoneMeta holds the metadata associated to a Zone. type ZoneMeta struct { // Id is the Zone's identifier. - Id Identifier `json:"id"` + Id Identifier `json:"id" swaggertype:"string"` // IdAuthor is the User's identifier for the current Zone. - IdAuthor Identifier `json:"id_author"` + IdAuthor Identifier `json:"id_author" swaggertype:"string"` // DefaultTTL is the TTL to use when no TTL has been defined for a record in this Zone. DefaultTTL uint32 `json:"default_ttl"`