feat: initial dnslink support

This commit is contained in:
Derrick Hammer 2023-08-15 02:11:55 -04:00
parent 3e80bb43fa
commit cd2f63eb72
Signed by: pcfreak30
GPG Key ID: C997C339BE476FF2
8 changed files with 278 additions and 27 deletions

View File

@ -50,13 +50,13 @@ func sendErrorCustom(ctx iris.Context, err error, customError error, irisError i
return false
}
func internalError(ctx iris.Context, err error) bool {
func InternalError(ctx iris.Context, err error) bool {
return sendErrorCustom(ctx, err, nil, iris.StatusInternalServerError)
}
func internalErrorCustom(ctx iris.Context, err error, customError error) bool {
return sendErrorCustom(ctx, err, customError, iris.StatusInternalServerError)
}
func sendError(ctx iris.Context, err error, irisError int) bool {
func SendError(ctx iris.Context, err error, irisError int) bool {
return sendErrorCustom(ctx, err, nil, irisError)
}

View File

@ -13,7 +13,7 @@ import (
"io"
)
var errStreamDone = errors.New("done")
var ErrStreamDone = errors.New("done")
type FilesController struct {
Controller
@ -36,21 +36,21 @@ func (f *FilesController) PostUpload() {
upload, err := files.Upload(file, meta.Size, nil, auth.GetCurrentUserId(ctx))
if internalError(ctx, err) {
if InternalError(ctx, err) {
logger.Get().Debug("failed uploading file", zap.Error(err))
return
}
err = files.Pin(upload.Hash, upload.AccountID)
if internalError(ctx, err) {
if InternalError(ctx, err) {
logger.Get().Debug("failed pinning file", zap.Error(err))
return
}
cidString, err := cid.EncodeString(upload.Hash, uint64(meta.Size))
if internalError(ctx, err) {
if InternalError(ctx, err) {
logger.Get().Debug("failed creating cid", zap.Error(err))
return
}
@ -65,20 +65,20 @@ func (f *FilesController) PostUpload() {
func (f *FilesController) GetDownloadBy(cidString string) {
ctx := f.Ctx
hashHex, valid := validateCid(cidString, true, ctx)
hashHex, valid := ValidateCid(cidString, true, ctx)
if !valid {
return
}
download, err := files.Download(hashHex)
if internalError(ctx, err) {
if InternalError(ctx, err) {
logger.Get().Debug("failed fetching file", zap.Error(err))
return
}
err = passThroughStream(download, ctx)
if err != errStreamDone && internalError(ctx, err) {
err = PassThroughStream(download, ctx)
if err != ErrStreamDone && InternalError(ctx, err) {
logger.Get().Debug("failed streaming file", zap.Error(err))
}
}
@ -86,20 +86,20 @@ func (f *FilesController) GetDownloadBy(cidString string) {
func (f *FilesController) GetProofBy(cidString string) {
ctx := f.Ctx
hashHex, valid := validateCid(cidString, true, ctx)
hashHex, valid := ValidateCid(cidString, true, ctx)
if !valid {
return
}
proof, err := files.DownloadProof(hashHex)
if internalError(ctx, err) {
if InternalError(ctx, err) {
logger.Get().Debug("failed fetching file proof", zap.Error(err))
return
}
err = passThroughStream(proof, ctx)
if internalError(ctx, err) {
err = PassThroughStream(proof, ctx)
if InternalError(ctx, err) {
logger.Get().Debug("failed streaming file proof", zap.Error(err))
}
}
@ -107,7 +107,7 @@ func (f *FilesController) GetProofBy(cidString string) {
func (f *FilesController) GetStatusBy(cidString string) {
ctx := f.Ctx
hashHex, valid := validateCid(cidString, false, ctx)
hashHex, valid := ValidateCid(cidString, false, ctx)
if !valid {
return
@ -136,14 +136,14 @@ func (f *FilesController) GetStatusBy(cidString string) {
func (f *FilesController) PostPinBy(cidString string) {
ctx := f.Ctx
hashHex, valid := validateCid(cidString, true, ctx)
hashHex, valid := ValidateCid(cidString, true, ctx)
if !valid {
return
}
err := files.Pin(hashHex, auth.GetCurrentUserId(ctx))
if internalError(ctx, err) {
if InternalError(ctx, err) {
logger.Get().Error(err.Error())
return
}
@ -155,9 +155,9 @@ func (f *FilesController) GetUploadLimit() {
f.respondJSON(&response.UploadLimit{Limit: f.Ctx.Application().ConfigurationReadOnly().GetPostMaxMemory()})
}
func validateCid(cidString string, validateStatus bool, ctx iris.Context) (string, bool) {
func ValidateCid(cidString string, validateStatus bool, ctx iris.Context) (string, bool) {
_, err := cid.Valid(cidString)
if sendError(ctx, err, iris.StatusBadRequest) {
if SendError(ctx, err, iris.StatusBadRequest) {
logger.Get().Debug("invalid cid", zap.Error(err))
return "", false
}
@ -170,7 +170,7 @@ func validateCid(cidString string, validateStatus bool, ctx iris.Context) (strin
if status == files.STATUS_NOT_FOUND {
err := errors.New("cid not found")
sendError(ctx, errors.New("cid not found"), iris.StatusNotFound)
SendError(ctx, errors.New("cid not found"), iris.StatusNotFound)
logger.Get().Debug("cid not found", zap.Error(err))
return "", false
}
@ -179,12 +179,12 @@ func validateCid(cidString string, validateStatus bool, ctx iris.Context) (strin
return hashHex, true
}
func passThroughStream(stream io.Reader, ctx iris.Context) error {
func PassThroughStream(stream io.Reader, ctx iris.Context) error {
closed := false
err := ctx.StreamWriter(func(w io.Writer) error {
if closed {
return errStreamDone
return ErrStreamDone
}
count, err := io.CopyN(w, stream, 1024)
@ -205,7 +205,7 @@ func passThroughStream(stream io.Reader, ctx iris.Context) error {
return nil
})
if err == errStreamDone {
if err == ErrStreamDone {
err = nil
}

View File

@ -53,7 +53,7 @@ func Init() {
}
// Automatically migrate the database schema based on the model definitions.
err = db.Migrator().AutoMigrate(&model.Account{}, &model.Key{}, &model.KeyChallenge{}, &model.LoginSession{}, &model.Upload{}, &model.Pin{}, &model.Tus{})
err = db.Migrator().AutoMigrate(&model.Account{}, &model.Key{}, &model.KeyChallenge{}, &model.LoginSession{}, &model.Upload{}, &model.Pin{}, &model.Tus{}, &model.Dnslink{})
if err != nil {
panic(fmt.Errorf("Database setup failed database type: %s \n", err))
}

225
dnslink/dnslink.go Normal file
View File

@ -0,0 +1,225 @@
package dnslink
import (
"errors"
"git.lumeweb.com/LumeWeb/portal/cid"
"git.lumeweb.com/LumeWeb/portal/controller"
"git.lumeweb.com/LumeWeb/portal/db"
"git.lumeweb.com/LumeWeb/portal/logger"
"git.lumeweb.com/LumeWeb/portal/model"
"git.lumeweb.com/LumeWeb/portal/service/files"
dnslink "github.com/dnslink-std/go"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
"github.com/vmihailenco/msgpack/v5"
"go.uber.org/zap"
"io"
"path/filepath"
"strings"
)
var (
ErrFailedReadAppManifest = errors.New("failed to read app manifest")
ErrInvalidAppManifest = errors.New("invalid app manifest")
)
type CID string
type ExtraMetadata map[string]interface{}
type WebAppMetadata struct {
Schema string `msgpack:"$schema,omitempty"`
Type string `msgpack:"type"`
Name string `msgpack:"name,omitempty"`
TryFiles []string `msgpack:"tryFiles,omitempty"`
ErrorPages map[string]string `msgpack:"errorPages,omitempty"`
Paths map[string]PathContent `msgpack:"paths"`
ExtraMetadata ExtraMetadata `msgpack:"extraMetadata,omitempty"`
}
type PathContent struct {
CID CID `msgpack:"cid"`
ContentType string `msgpack:"contentType,omitempty"`
}
func Handler(ctx *context.Context) {
record := model.Dnslink{}
domain := ctx.Request().Host
if err := db.Get().Model(&model.Dnslink{Domain: domain}).First(&record).Error; err != nil {
ctx.StopWithStatus(iris.StatusNotFound)
return
}
ret, err := dnslink.Resolve(domain)
if err != nil {
switch e := err.(type) {
default:
ctx.StopWithStatus(iris.StatusInternalServerError)
return
case dnslink.DNSRCodeError:
if e.DNSRCode == 3 {
ctx.StopWithStatus(iris.StatusNotFound)
return
}
}
}
if ret.Links["sia"] == nil || len(ret.Links["sia"]) == 0 {
ctx.StopWithStatus(iris.StatusNotFound)
return
}
appManifest := ret.Links["sia"][0]
decodedCid, valid := controller.ValidateCid(appManifest.Identifier, true, ctx)
if !valid {
return
}
manifest := fetchManifest(ctx, decodedCid)
if manifest == nil {
return
}
path := ctx.Path()
if strings.HasSuffix(path, "/") || filepath.Ext(path) == "" {
var directoryIndex *PathContent
for _, indexFile := range manifest.TryFiles {
path, exists := manifest.Paths[indexFile]
if !exists {
continue
}
_, err := cid.Valid(string(manifest.Paths[indexFile].CID))
if err != nil {
continue
}
cidObject, _ := cid.Decode(string(path.CID))
hashHex := cidObject.StringHash()
status := files.Status(hashHex)
if status == files.STATUS_NOT_FOUND {
continue
}
if status == files.STATUS_UPLOADED {
directoryIndex = &path
break
}
}
if directoryIndex == nil {
ctx.StopWithStatus(iris.StatusNotFound)
return
}
file, err := fetchFile(directoryIndex)
if maybeHandleFileError(err, ctx) {
return
}
ctx.Header("Content-Type", directoryIndex.ContentType)
streamFile(file, ctx)
return
}
requestedPath, exists := manifest.Paths[path]
if !exists {
ctx.StopWithStatus(iris.StatusNotFound)
return
}
file, err := fetchFile(&requestedPath)
if maybeHandleFileError(err, ctx) {
return
}
ctx.Header("Content-Type", requestedPath.ContentType)
streamFile(file, ctx)
}
func maybeHandleFileError(err error, ctx *context.Context) bool {
if err != nil {
if err == files.ErrInvalidFile {
controller.SendError(ctx, err, iris.StatusNotFound)
return true
}
controller.SendError(ctx, err, iris.StatusInternalServerError)
}
return err != nil
}
func streamFile(stream io.Reader, ctx *context.Context) {
err := controller.PassThroughStream(stream, ctx)
if err != controller.ErrStreamDone && controller.InternalError(ctx, err) {
logger.Get().Debug("failed streaming file", zap.Error(err))
}
}
func fetchFile(path *PathContent) (io.Reader, error) {
_, err := cid.Valid(string(path.CID))
if err != nil {
return nil, err
}
cidObject, _ := cid.Decode(string(path.CID))
hashHex := cidObject.StringHash()
status := files.Status(hashHex)
if status == files.STATUS_NOT_FOUND {
return nil, errors.New("cid not found")
}
if status == files.STATUS_UPLOADED {
stream, err := files.Download(hashHex)
if err != nil {
return nil, err
}
return stream, nil
}
return nil, errors.New("cid not found")
}
func fetchManifest(ctx iris.Context, hash string) *WebAppMetadata {
stream, err := files.Download(hash)
if err != nil {
if errors.Is(err, files.ErrInvalidFile) {
controller.SendError(ctx, err, iris.StatusNotFound)
return nil
}
controller.SendError(ctx, err, iris.StatusInternalServerError)
}
var metadata WebAppMetadata
data, err := io.ReadAll(stream)
if err != nil {
logger.Get().Debug(ErrFailedReadAppManifest.Error(), zap.Error(err))
controller.SendError(ctx, ErrFailedReadAppManifest, iris.StatusInternalServerError)
return nil
}
err = msgpack.Unmarshal(data, &metadata)
if err != nil {
logger.Get().Debug(ErrFailedReadAppManifest.Error(), zap.Error(err))
controller.SendError(ctx, ErrFailedReadAppManifest, iris.StatusInternalServerError)
return nil
}
if metadata.Type != "web_app" {
logger.Get().Debug(ErrInvalidAppManifest.Error())
controller.SendError(ctx, ErrInvalidAppManifest, iris.StatusInternalServerError)
return nil
}
return &metadata
}

4
go.mod
View File

@ -3,6 +3,7 @@ module git.lumeweb.com/LumeWeb/portal
go 1.18
require (
github.com/dnslink-std/go v0.6.0
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/go-resty/resty/v2 v2.7.0
github.com/golang-queue/queue v0.1.3
@ -16,6 +17,7 @@ require (
github.com/spf13/viper v1.16.0
github.com/swaggo/swag v1.16.1
github.com/tus/tusd v1.11.0
github.com/vmihailenco/msgpack/v5 v5.3.5
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.10.0
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
@ -74,6 +76,7 @@ require (
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/mediocregopher/radix/v3 v3.8.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.24 // indirect
github.com/miekg/dns v1.1.43 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
@ -94,7 +97,6 @@ require (
github.com/tdewolff/minify/v2 v2.12.7 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yosssi/ace v0.0.5 // indirect
go.uber.org/atomic v1.11.0 // indirect

9
go.sum
View File

@ -691,6 +691,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dnslink-std/go v0.6.0 h1:i+5HdSFNrpKxozvyebtvUgjXdqm7SW35NWkO4mnxyjQ=
github.com/dnslink-std/go v0.6.0/go.mod h1:LZoJk4C4PpPZdJhsfi3ADdOVz7teVr1q2MZTtRrCTLE=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@ -765,6 +767,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@ -998,6 +1002,8 @@ github.com/mediocregopher/radix/v3 v3.8.1 h1:rOkHflVuulFKlwsLY01/M2cM2tWCjDoETcM
github.com/mediocregopher/radix/v3 v3.8.1/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw=
github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
@ -1109,6 +1115,7 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -1297,6 +1304,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -1416,6 +1424,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -6,6 +6,7 @@ import (
"git.lumeweb.com/LumeWeb/portal/config"
"git.lumeweb.com/LumeWeb/portal/controller"
"git.lumeweb.com/LumeWeb/portal/db"
"git.lumeweb.com/LumeWeb/portal/dnslink"
_ "git.lumeweb.com/LumeWeb/portal/docs"
"git.lumeweb.com/LumeWeb/portal/logger"
"git.lumeweb.com/LumeWeb/portal/middleware"
@ -66,7 +67,8 @@ func main() {
app.UseRouter(cors.New().Handler())
// Serve static files from the embedded directory at the app's root path
app.HandleDir("/", embedFrontend)
_ = embedFrontend
// app.HandleDir("/", embedFrontend)
api := app.Party("/api")
v1 := api.Party("/v1")
@ -94,7 +96,7 @@ func main() {
}
tusRoute.Any("/{fileparam:path}", fromStd(http.StripPrefix(v1.GetRelPath()+tus.TUS_API_PATH+"/", tusHandler)))
tusRoute.Post("/", fromStd(http.StripPrefix(tusRoute.GetRelPath()+tus.TUS_API_PATH, tusHandler)))
tusRoute.Post("/{p:path}", fromStd(http.StripPrefix(tusRoute.GetRelPath()+tus.TUS_API_PATH, tusHandler)))
app.Handle(new(controller.FilesController))
})
@ -114,6 +116,8 @@ func main() {
// And the wildcard one for index.html, *.js, *.css and e.t.c.
app.Get("/swagger/{any:path}", swaggerUI)
app.Party("*").Any("*", dnslink.Handler)
// Start the Iris app and listen for incoming requests on port 80
err := app.Listen(":8080", func(app *iris.Application) {
routes := app.GetRoutes()

11
model/dnslink.go Normal file
View File

@ -0,0 +1,11 @@
package model
import (
"gorm.io/gorm"
)
type Dnslink struct {
gorm.Model
ID uint `gorm:"primaryKey" gorm:"AUTO_INCREMENT"`
Domain string `gorm:"uniqueIndex"`
}