diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..95c39d4 --- /dev/null +++ b/.air.toml @@ -0,0 +1,60 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] +args_bin = [] +bin = "./tmp/codex" +cmd = "go build -o ./tmp/codex ./api/cmd/codex" +delay = 1000 +exclude_dir = [ + ".git", + "tmp", + "vendor", + "testdata", + "node_modules", + ".vscode", + ".idea", +] +exclude_file = [] +exclude_regex = ["_test\\.go$", "\\.git", "\\.DS_Store"] +exclude_unchanged = false +follow_symlink = false +full_bin = "" +include_dir = ["api"] +include_ext = ["go", "mod", "sum", "env"] +include_file = [] +kill_delay = "0s" +log = "build-errors.log" +poll = false +poll_interval = 0 +post_cmd = [] +pre_cmd = [] +rerun = false +rerun_delay = 500 +send_interrupt = false +stop_on_error = true + +[color] +app = "" +build = "yellow" +main = "magenta" +runner = "green" +watcher = "cyan" + +[log] +main_only = false +silent = false +time = false + +[misc] +clean_on_exit = false + +[proxy] +app_port = 0 +enabled = false +proxy_port = 0 + +[screen] +clear_on_rebuild = false +keep_scroll = true diff --git a/.gitignore b/.gitignore index 88aa15e..cdcafa2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env +tmp firestore.json diff --git a/api/internal/common/timestamp.go b/api/internal/common/timestamp.go new file mode 100644 index 0000000..e34fbc1 --- /dev/null +++ b/api/internal/common/timestamp.go @@ -0,0 +1,15 @@ +package common + +import "time" + +func TimeStamp(date string) string { + if date != "" { + t, err := time.Parse("2006-01-02 15:04:05", date) + if err != nil { + return time.Now().Format("2006-01-02 15:04:05") + } + return t.Format("2006-01-02 15:04:05") + } + + return time.Now().Format("2006-01-02 15:04:05") +} diff --git a/api/internal/domain/chapter.go b/api/internal/domain/chapter.go index 6cdb8ba..945e5d0 100644 --- a/api/internal/domain/chapter.go +++ b/api/internal/domain/chapter.go @@ -14,13 +14,26 @@ type CursorResponse struct { NextCursor string `json:"next_cursor"` } +// Chapter struct used on backend type Chapter struct { ID string `json:"id"` Title string `json:"title"` Author string `json:"author"` Description string `json:"description"` CreatedAt string `json:"creation_date"` - UploadedAt string `json:"upload_date"` + UpdatedAt string `json:"update_date"` + Content string `json:"content"` + Index int `json:"index"` + Deleted bool `json:"deleted"` +} + +// Chapter struct used on frontend +type FrontendChapter struct { + ID string `json:"id"` + Title string `json:"title"` + Author string `json:"author"` + Description string `json:"description"` + CreatedAt string `json:"creation_date"` UpdatedAt string `json:"update_date"` Content string `json:"content"` } diff --git a/api/internal/domain/novel.go b/api/internal/domain/novel.go index 951a873..833da46 100644 --- a/api/internal/domain/novel.go +++ b/api/internal/domain/novel.go @@ -1,11 +1,22 @@ package domain +// Novel struct used on backend type Novel struct { ID string `json:"id"` Title string `json:"title"` Author string `json:"author"` Description string `json:"description"` CreatedAt string `json:"creation_date"` - UploadedAt string `json:"upload_date"` + UpdatedAt string `json:"update_date"` + Deleted bool `json:"deleted"` +} + +// Novel struct used on frontend +type FrontendNovel struct { + ID string `json:"id"` + Title string `json:"title"` + Author string `json:"author"` + Description string `json:"description"` + CreatedAt string `json:"creation_date"` UpdatedAt string `json:"update_date"` } diff --git a/api/internal/infrastructure/collections/chapters.go b/api/internal/infrastructure/collections/chapters.go index d07a9bb..b500a15 100644 --- a/api/internal/infrastructure/collections/chapters.go +++ b/api/internal/infrastructure/collections/chapters.go @@ -16,13 +16,11 @@ import ( "google.golang.org/grpc/status" ) -const Limit = 100 - func (c *Client) CursorPagination(options domain.CursorOptions, ctx context.Context) (*domain.CursorResponse, error) { coll := c.Client.Collection("novels").Doc(options.NovelID).Collection("chapters") query := coll.OrderBy(firestore.DocumentID, options.SortBy) - limit := min(max(options.Limit, 1), Limit) + limit := min(max(options.Limit, 1), 100) if options.Cursor == "" { snaps, err := query.Limit(limit + 1).Documents(ctx).GetAll() @@ -97,7 +95,10 @@ func (c *Client) BatchUploadChapters(novelId string, chapters []domain.Chapter, for i := 0; i < len(chapters); i += chunkSize { subset := chapters[i:min(i+chunkSize, len(chapters))] - bw := c.Client.BulkWriter(ctx) + batchCtx, cancel := context.WithTimeout(ctx, 300*time.Second) + defer cancel() + + bw := c.Client.BulkWriter(batchCtx) jobs := make([]*firestore.BulkWriterJob, 0, len(subset)) for _, chap := range subset { @@ -124,6 +125,8 @@ func (c *Client) BatchUploadChapters(novelId string, chapters []domain.Chapter, } } } + + time.Sleep(100 * time.Millisecond) } return nil diff --git a/api/internal/interfaces/rest/handlers/novels.go b/api/internal/interfaces/rest/handlers/novels.go index ab6476e..0983969 100644 --- a/api/internal/interfaces/rest/handlers/novels.go +++ b/api/internal/interfaces/rest/handlers/novels.go @@ -10,6 +10,42 @@ import ( "github.com/gin-gonic/gin" ) +func EPUBNovel(c *gin.Context) { + ctx := c.Request.Context() + defer ctx.Done() + + defer func() { + if c.Request.MultipartForm != nil { + c.Request.MultipartForm.RemoveAll() + } + }() + + epubFile, err := c.FormFile("file") + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ + "error": "Failed to get EPUB file: " + err.Error(), + }) + return + } + + err = firestore_services.CreateNovelFromEPUB(epubFile, ctx) + if e, ok := err.(*cmn.Error); ok { + c.AbortWithStatusJSON(e.StatusCode(), gin.H{ + "error": "Failed to upload EPUB file: " + e.Error(), + }) + return + } else if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to upload EPUB file: " + err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "EPUB file uploaded successfully", + }) +} + func FindNovel(c *gin.Context) { ctx := c.Request.Context() defer ctx.Done() diff --git a/api/internal/interfaces/rest/routes.go b/api/internal/interfaces/rest/routes.go index 704c1a9..e9fa68f 100644 --- a/api/internal/interfaces/rest/routes.go +++ b/api/internal/interfaces/rest/routes.go @@ -39,6 +39,7 @@ func RegisteredRoutes(r *gin.Engine) { })) r.Use(firestore_middleware.RateLimiter()) + r.MaxMultipartMemory = 32 << 20 // 32 MB client := r.Group("/") { @@ -53,7 +54,7 @@ func RegisteredRoutes(r *gin.Engine) { { manage.POST("/novel", firestore_middleware.ValidateToken(), firestore_handlers.CreateNovel) manage.POST("/:novel/chapter", firestore_middleware.ValidateToken(), firestore_handlers.CreateChapter) - manage.POST("/:novel", firestore_middleware.ValidateToken(), firestore_handlers.BatchUploadChapters) + manage.POST("/epub", firestore_middleware.ValidateToken(), firestore_handlers.EPUBNovel) manage.PUT("/:novel", firestore_middleware.ValidateToken(), firestore_handlers.UpdateNovel) manage.PUT("/:novel/:chapter", firestore_middleware.ValidateToken(), firestore_handlers.UpdateChapter) manage.DELETE("/:novel", firestore_middleware.ValidateToken(), firestore_handlers.DeleteNovel) diff --git a/api/internal/usecases/collections/chapters.go b/api/internal/usecases/collections/chapters.go index f8a5cb2..1eb7f27 100644 --- a/api/internal/usecases/collections/chapters.go +++ b/api/internal/usecases/collections/chapters.go @@ -52,7 +52,7 @@ func BatchUploadChapters(novelId string, chapters []domain.Chapter, ctx context. chapter.ID = id chapter.CreatedAt = time.Now().Format("2006-01-02 15:04:05") chapter.UpdatedAt = time.Now().Format("2006-01-02 15:04:05") - chapter.UploadedAt = time.Now().Format("2006-01-02 15:04:05") + chapter.Deleted = false final = append(final, chapter) } @@ -89,7 +89,7 @@ func CreateChapter(novelId string, chapter domain.Chapter, ctx context.Context) chapter.ID = id chapter.CreatedAt = time.Now().Format("2006-01-02 15:04:05") chapter.UpdatedAt = time.Now().Format("2006-01-02 15:04:05") - chapter.UploadedAt = time.Now().Format("2006-01-02 15:04:05") + chapter.Deleted = false err = c.CreateChapter(novelId, chapter, ctx) if err != nil { diff --git a/api/internal/usecases/collections/novels.go b/api/internal/usecases/collections/novels.go index fdc0284..bf08930 100644 --- a/api/internal/usecases/collections/novels.go +++ b/api/internal/usecases/collections/novels.go @@ -7,10 +7,125 @@ import ( firestore_collections "Codex-Backend/api/internal/infrastructure/collections" "context" "errors" + "io" + "mime/multipart" "net/http" + "strings" "time" + + htmltomarkdown "github.com/JohannesKaufmann/html-to-markdown/v2" + "github.com/PuerkitoBio/goquery" + "github.com/timsims/pamphlet" ) +// Remove HTML tags and convert the body to Markdown +func cleanHtml(input string) (string, error) { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(input)) + if err != nil { + return "", err + } + + doc.Find("head, script, style, nav").Remove() + + html, err := doc.Find("body").Html() + if err != nil { + return "", err + } + + return htmltomarkdown.ConvertString(html) +} + +func CreateNovelFromEPUB(epubFile *multipart.FileHeader, ctx context.Context) error { + multipartFIle, err := epubFile.Open() + if err != nil { + return err + } + defer multipartFIle.Close() + + data, err := io.ReadAll(multipartFIle) + if err != nil { + return err + } + + parser, err := pamphlet.OpenBytes(data) + if err != nil { + return err + } + defer parser.Close() + + book := parser.GetBook() + + // Create Novel + + id, err := cmn.GenerateID("novel") + if err != nil { + return err + } + + description, err := cleanHtml(book.Description) + if err != nil { + return err + } + + novel := &domain.Novel{ + ID: id, + Title: book.Title, + Author: book.Author, + Description: description, + CreatedAt: cmn.TimeStamp(book.Date), + UpdatedAt: cmn.TimeStamp(""), + Deleted: false, + } + + err, id = CreateNovel(*novel, ctx) + if err != nil { + return err + } + + // Create chapters + + rawChapters := book.Chapters + + c_id, err := cmn.GenerateID("chapter") + if err != nil { + return err + } + + chapters := make([]domain.Chapter, len(rawChapters)) + for i, chapter := range rawChapters { + rawContent, err := chapter.GetContent() + if err != nil { + return err + } + + content, err := cleanHtml(rawContent) + if err != nil { + return err + } + + chapter := &domain.Chapter{ + ID: c_id, + Title: chapter.Title, + Author: book.Author, + Description: "", + CreatedAt: cmn.TimeStamp(""), + UpdatedAt: cmn.TimeStamp(""), + Content: content, + Index: i, + Deleted: false, + } + + chapters[i] = *chapter + } + + err = BatchUploadChapters(id, chapters, ctx) + if err != nil { + return err + } + + return nil +} + func CreateNovel(novel domain.Novel, ctx context.Context) (error, string) { client, err := firestore_client.FirestoreClient() if err != nil { @@ -28,7 +143,7 @@ func CreateNovel(novel domain.Novel, ctx context.Context) (error, string) { novel.ID = id novel.CreatedAt = time.Now().Format("2006-01-02 15:04:05") novel.UpdatedAt = time.Now().Format("2006-01-02 15:04:05") - novel.UploadedAt = time.Now().Format("2006-01-02 15:04:05") + novel.Deleted = false err = c.CreateNovel(novel, ctx) if err != nil { diff --git a/go.mod b/go.mod index ace40cb..f7d8abf 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,16 @@ toolchain go1.24.0 require ( cloud.google.com/go/firestore v1.18.0 firebase.google.com/go v3.13.0+incompatible + github.com/JohannesKaufmann/html-to-markdown/v2 v2.3.3 + github.com/PuerkitoBio/goquery v1.10.3 github.com/fvbommel/sortorder v1.1.0 github.com/gin-contrib/cors v1.7.3 github.com/gin-gonic/gin v1.10.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/joho/godotenv v1.5.1 github.com/oklog/ulid/v2 v2.1.1 - golang.org/x/crypto v0.36.0 + github.com/timsims/pamphlet v0.1.6 + golang.org/x/crypto v0.41.0 golang.org/x/time v0.10.0 google.golang.org/api v0.214.0 google.golang.org/grpc v1.73.0 @@ -27,6 +30,8 @@ require ( cloud.google.com/go/iam v1.2.2 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect cloud.google.com/go/storage v1.43.0 // indirect + github.com/JohannesKaufmann/dom v0.2.0 // indirect + github.com/andybalholm/cascadia v1.3.3 // indirect github.com/bytedance/sonic v1.12.9 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/cloudwego/base64x v0.1.5 // indirect @@ -60,11 +65,11 @@ require ( go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect golang.org/x/arch v0.14.0 // indirect - golang.org/x/net v0.38.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect diff --git a/go.sum b/go.sum index 29bc8b8..ec1d49c 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,14 @@ cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyX cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= +github.com/JohannesKaufmann/dom v0.2.0 h1:1bragmEb19K8lHAqgFgqCpiPCFEZMTXzOIEjuxkUfLQ= +github.com/JohannesKaufmann/dom v0.2.0/go.mod h1:57iSUl5RKric4bUkgos4zu6Xt5LMHUnw3TF1l5CbGZo= +github.com/JohannesKaufmann/html-to-markdown/v2 v2.3.3 h1:r3fokGFRDk/8pHmwLwJ8zsX4qiqfS1/1TZm2BH8ueY8= +github.com/JohannesKaufmann/html-to-markdown/v2 v2.3.3/go.mod h1:HtsP+1Fchp4dVvaiIsLHAl/yqL3H1YLwqLC9kNwqQEg= +github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= +github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= +github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= +github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ= github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -63,6 +71,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -106,6 +115,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY= +github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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= @@ -118,11 +131,15 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/timsims/pamphlet v0.1.6 h1:6EX2hJeU4GbAu3AclA1apxVVbPug6KuzXsIP1NqNVb8= +github.com/timsims/pamphlet v0.1.6/go.mod h1:i3lq9uyxZeAn2HT1c2+5IJ50oc9tH1K31gHT3WDYi8M= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo= +github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -145,41 +162,82 @@ golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA=