diff --git a/api/.idea/dataSources.xml b/api/.idea/dataSources.xml new file mode 100644 index 0000000..0dd3072 --- /dev/null +++ b/api/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/downloader + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/api/go.mod b/api/go.mod index 73b9a32..de72539 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,13 +3,27 @@ module downloader go 1.17 require ( + github.com/fatih/color v1.13.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-chi/chi v1.5.4 // indirect github.com/go-chi/render v1.0.1 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/ovh/configstore v0.5.2 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/uptrace/bun v1.0.20 // indirect + github.com/uptrace/bun/dialect/pgdialect v1.0.20 // indirect + github.com/uptrace/bun/driver/pgdriver v1.0.20 // indirect + github.com/uptrace/bun/extra/bundebug v1.0.20 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + mellium.im/sasl v0.2.1 // indirect ) diff --git a/api/go.sum b/api/go.sum index 86439ce..e02ece2 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,6 +1,8 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= @@ -9,9 +11,17 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/ovh/configstore v0.5.2 h1:VgraYqXx35W3ESBuGBzKdaa23FOVjJ7u1aY/zjeYcZw= github.com/ovh/configstore v0.5.2/go.mod h1:krvoiDcrESKjEOz6AnKD8EbCmqHHQyOzEJNhAIhB4+Y= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -20,6 +30,20 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun v1.0.20 h1:/T4p9C9tEN75U3cFMBr5njlP+rBfs4An8BmlQQPbcfE= +github.com/uptrace/bun v1.0.20/go.mod h1:Uv7z0z+7dXnUS9P5hMF0hdiM/4M+xOUHQCrZpyDrpRc= +github.com/uptrace/bun/dialect/pgdialect v1.0.20 h1:1Yajz0M2AhOzvxFEQSAQ8TpqzSRFxYOg+saksIQ0dmU= +github.com/uptrace/bun/dialect/pgdialect v1.0.20/go.mod h1:Z2UoOgTKHXgFOuInXsJKkNQJiFIaPkCvsj0EayOI2yk= +github.com/uptrace/bun/driver/pgdriver v1.0.20 h1:CEWHL5NS5FQIJAJxY40t0llwe8XxVlsblbgi9Upm0fA= +github.com/uptrace/bun/driver/pgdriver v1.0.20/go.mod h1:KAONvCIiI4A6HdMTZ8zCdGxh7P6+23Todz+bL8HRzV4= +github.com/uptrace/bun/extra/bundebug v1.0.20 h1:lwuGUMiqujR3NuGDKgJu1j7XL3LsULSv1MDFHlYBAGs= +github.com/uptrace/bun/extra/bundebug v1.0.20/go.mod h1:tDoi/zmjHkumthaCujwfI2+mni0G41HfJD4HC2oMdpk= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -28,24 +52,39 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab h1:rfJ1bsoJQQIAoAxTxB7bme+vHrNkRw8CqfsYh9w54cw= +golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -60,3 +99,5 @@ 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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= +mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ= diff --git a/api/internal/app/api/download/request_download.go b/api/internal/app/api/download/request_download.go index 0e9d3a5..f3756a7 100644 --- a/api/internal/app/api/download/request_download.go +++ b/api/internal/app/api/download/request_download.go @@ -29,13 +29,14 @@ func (dr *requestDownloadRequest) Bind(r *http.Request) error { } func (a *api) requestDownload(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() data := &requestDownloadRequest{} if err := render.Bind(r, data); err != nil { _ = render.Render(w, r, responses.ErrInvalidRequest(err)) return } - download, err := a.drService.Schedule(data.Link) + download, err := a.drService.Schedule(ctx, data.Link) if err != nil { _ = render.Render(w, r, responses.ErrInvalidRequest(err)) return @@ -46,8 +47,9 @@ func (a *api) requestDownload(w http.ResponseWriter, r *http.Request) { } func (a *api) getDownloads(writer http.ResponseWriter, request *http.Request) { + ctx := request.Context() active := request.URL.Query().Get("active") == "true" - downloads, err := a.drService.GetAll(active) + downloads, err := a.drService.GetAll(ctx, active) if err != nil { _ = render.Render(writer, request, responses.ErrInvalidRequest(err)) return @@ -61,9 +63,10 @@ func (a *api) getDownloads(writer http.ResponseWriter, request *http.Request) { } func (a *api) getDownloadById(w http.ResponseWriter, r *http.Request) { - downloadId := r.Context().Value("downloadId").(string) + ctx := r.Context() + downloadId := ctx.Value("downloadId").(string) - download, err := a.drService.Get(downloadId) + download, err := a.drService.Get(ctx, downloadId) if err != nil { _ = render.Render(w, r, responses.ErrNotFound()) return @@ -80,9 +83,14 @@ func newRequestDownloadResponse(download *entities.Download) *requestDownloadRes } func newDownloadsResponse(downloads []*entities.Download) []render.Renderer { - list := []render.Renderer{} + var list []render.Renderer for _, download := range downloads { list = append(list, newRequestDownloadResponse(download)) } + + if len(list) == 0 { + return []render.Renderer{} + } + return list } diff --git a/api/internal/app/persistence/database.go b/api/internal/app/persistence/database.go new file mode 100644 index 0000000..183db59 --- /dev/null +++ b/api/internal/app/persistence/database.go @@ -0,0 +1,29 @@ +package persistence + +import ( + "context" + "database/sql" + "downloader/internal/app/persistence/migrations" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/driver/pgdriver" + "github.com/uptrace/bun/extra/bundebug" + "github.com/uptrace/bun/migrate" +) + +func NewPostgresDB() *bun.DB { + dsn := "postgres://downloader:downloadersecret@localhost:5432/downloader?sslmode=disable" + sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) + + db := bun.NewDB(sqldb, pgdialect.New()) + db.AddQueryHook(bundebug.NewQueryHook( + bundebug.WithVerbose(true), + bundebug.FromEnv("BUNDEBUG"))) + + migrator := migrate.NewMigrator(db, migrations.Migrations) + bgCtx := context.Background() + migrator.Init(bgCtx) + migrator.Migrate(bgCtx) + + return db +} diff --git a/api/internal/app/persistence/migrations/20210505110847_add_download_stable.go b/api/internal/app/persistence/migrations/20210505110847_add_download_stable.go new file mode 100644 index 0000000..9eb9f33 --- /dev/null +++ b/api/internal/app/persistence/migrations/20210505110847_add_download_stable.go @@ -0,0 +1,27 @@ +package migrations + +import ( + "context" + "downloader/internal/core/ports/download_request/sql" + "github.com/uptrace/bun" +) + +func init() { + Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + _, err := db.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`) + if err != nil { + return err + } + + db.RegisterModel((*sql.Download)(nil)) + _, err = db.NewCreateTable(). + Model((*sql.Download)(nil)). + Exec(ctx) + return err + }, func(ctx context.Context, db *bun.DB) error { + _, err := db.NewDropTable(). + Model((*sql.Download)(nil)). + Exec(ctx) + return err + }) +} diff --git a/api/internal/app/persistence/migrations/main.go b/api/internal/app/persistence/migrations/main.go new file mode 100644 index 0000000..478e10c --- /dev/null +++ b/api/internal/app/persistence/migrations/main.go @@ -0,0 +1,17 @@ +package migrations + +import ( + "embed" + "github.com/uptrace/bun/migrate" +) + +var Migrations = migrate.NewMigrations() + +// go:embed *.sql +var sqlMigrations embed.FS + +func init() { + if err := Migrations.Discover(sqlMigrations); err != nil { + panic(err) + } +} diff --git a/api/internal/app/router/router.go b/api/internal/app/router/router.go index d9901d1..7679990 100644 --- a/api/internal/app/router/router.go +++ b/api/internal/app/router/router.go @@ -3,7 +3,8 @@ package router import ( "downloader/internal/app/api/download" "downloader/internal/app/infrastructure/logger" - "downloader/internal/core/ports/download_request/in_memory" + "downloader/internal/app/persistence" + "downloader/internal/core/ports/download_request/sql" "downloader/internal/core/ports/downloadhandler" "downloader/internal/core/ports/filehandler/mover/local" "downloader/internal/core/ports/fileorchestrator" @@ -64,7 +65,9 @@ func setupDownloadRoute(router *router) { destinationHandler := destinationhandler.New(mover) fileOrchestrator := fileorchestrator.New(newLogger, sourceHandler, destinationHandler) - drRepository := in_memory.NewInMemoryRepository(newLogger) + db := persistence.NewPostgresDB() + drRepository := sql.NewDownloadRequestSqlRepository(db, newLogger) + //drRepository := in_memory.NewInMemoryRepository(newLogger) //dlHandler := downloadhandler.NewYoutubeDlDownloader(newLogger) dlHandler := downloadhandler.NewYtDlpDownloader(newLogger) ondlHandler := handlers.New(drRepository, newLogger) diff --git a/api/internal/core/ports/download_request/repository.go b/api/internal/core/ports/download_request/repository.go index a4a60f4..7a53fec 100644 --- a/api/internal/core/ports/download_request/repository.go +++ b/api/internal/core/ports/download_request/repository.go @@ -1,10 +1,13 @@ package download_request -import "downloader/internal/core/entities" +import ( + "context" + "downloader/internal/core/entities" +) type Repository interface { - Create(download *entities.Download) (*entities.Download, error) - GetById(id string) (*entities.Download, error) - Update(download *entities.Download) error - Get(active bool) ([]*entities.Download, error) + Create(ctx context.Context, download *entities.Download) (*entities.Download, error) + GetById(ctx context.Context, id string) (*entities.Download, error) + Update(ctx context.Context, download *entities.Download) error + Get(ctx context.Context, active bool) ([]*entities.Download, error) } diff --git a/api/internal/core/ports/download_request/sql/download.go b/api/internal/core/ports/download_request/sql/download.go new file mode 100644 index 0000000..2705af0 --- /dev/null +++ b/api/internal/core/ports/download_request/sql/download.go @@ -0,0 +1,29 @@ +package sql + +import ( + "context" + "github.com/uptrace/bun" + "time" +) + +type Download struct { + bun.BaseModel `bun:"table:downloads,alias:d"` + ID string `bun:"id,pk,type:uuid,default:uuid_generate_v4()"` + Status string `bun:"status,notnull"` + Link string `bun:"link,notnull"` + CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:"updated_at,nullzero,notnull,default:current_timestamp"` +} + +var _ bun.BeforeAppendModelHook = (*Download)(nil) + +func (u *Download) BeforeAppendModel(ctx context.Context, query bun.Query) error { + switch query.(type) { + case *bun.InsertQuery: + u.CreatedAt = time.Now() + u.UpdatedAt = time.Now() + case *bun.UpdateQuery: + u.UpdatedAt = time.Now() + } + return nil +} diff --git a/api/internal/core/ports/download_request/sql/repository.go b/api/internal/core/ports/download_request/sql/repository.go new file mode 100644 index 0000000..be40eb1 --- /dev/null +++ b/api/internal/core/ports/download_request/sql/repository.go @@ -0,0 +1,102 @@ +package sql + +import ( + "context" + "downloader/internal/core/entities" + "github.com/uptrace/bun" + "go.uber.org/zap" +) + +type repository struct { + db *bun.DB + logger *zap.SugaredLogger +} + +func NewDownloadRequestSqlRepository(db *bun.DB, logger *zap.SugaredLogger) *repository { + return &repository{ + db: db, + logger: logger, + } +} + +func (r repository) Create(ctx context.Context, download *entities.Download) (*entities.Download, error) { + insertDownload := &Download{ + ID: download.ID, + Status: download.Status, + Link: download.Link, + } + + _, err := r.db.NewInsert(). + Model(insertDownload). + Returning("*"). + Exec(ctx) + if err != nil { + return nil, err + } + + return &entities.Download{ + ID: insertDownload.ID, + Status: insertDownload.Status, + Link: insertDownload.Link, + }, nil +} + +func (r repository) GetById(ctx context.Context, id string) (*entities.Download, error) { + download := &Download{ + ID: id, + } + + err := r.db.NewSelect(). + Model(download). + WherePK(). + Scan(ctx) + if err != nil { + return nil, err + } + + return &entities.Download{ + ID: download.ID, + Status: download.Status, + Link: download.Link, + }, nil +} + +func (r repository) Update(ctx context.Context, download *entities.Download) error { + updateDownload := &Download{ + ID: download.ID, + Status: download.Status, + Link: download.Link, + } + + _, err := r.db.NewUpdate(). + Model(updateDownload). + ExcludeColumn("created_at"). + WherePK(). + Exec(ctx) + + return err +} + +func (r repository) Get(ctx context.Context, active bool) ([]*entities.Download, error) { + var downloads []Download + err := r.db.NewSelect(). + Model(&downloads). + Column("id", "status", "link"). + Limit(20). + Order("created_at ASC"). + Scan(ctx) + if err != nil { + return nil, err + } + + var responseDownloads []*entities.Download + for _, download := range downloads { + responseDownloads = append(responseDownloads, &entities.Download{ + ID: download.ID, + Status: download.Status, + Link: download.Link, + }) + } + + return responseDownloads, nil +} diff --git a/api/internal/core/services/download/background.go b/api/internal/core/services/download/background.go index 6fd6a57..709aca0 100644 --- a/api/internal/core/services/download/background.go +++ b/api/internal/core/services/download/background.go @@ -1,7 +1,10 @@ package download -import "downloader/internal/core/entities" +import ( + "context" + "downloader/internal/core/entities" +) type BackgroundService interface { - Run(download *entities.Download) error + Run(ctx context.Context, download *entities.Download) error } diff --git a/api/internal/core/services/download/default/background.go b/api/internal/core/services/download/default/background.go index 673fbed..e65d25b 100644 --- a/api/internal/core/services/download/default/background.go +++ b/api/internal/core/services/download/default/background.go @@ -1,6 +1,7 @@ package _default import ( + "context" "downloader/internal/core/entities" "downloader/internal/core/ports/download_request" "downloader/internal/core/services/download" @@ -18,12 +19,13 @@ func NewLocalBackgroundService(repository download_request.Repository, logger *z return &localBackgroundService{repository: repository, logger: logger, downloader: downloader} } -func (l localBackgroundService) Run(download *entities.Download) error { +func (l localBackgroundService) Run(ctx context.Context, download *entities.Download) error { logger := l.logger.With("downloadId", download.ID) go func() { + longRunningCtx := context.TODO() download.Status = "started" - _ = l.repository.Update(download) + _ = l.repository.Update(longRunningCtx, download) err := l.downloader.Download(download.Link, download.ID) download.Status = "done" @@ -33,13 +35,13 @@ func (l localBackgroundService) Run(download *entities.Download) error { download.Status = "failed" } - err = l.repository.Update(download) + err = l.repository.Update(longRunningCtx, download) if err != nil { logger.Errorw("download request failed", "downloadLink", download.Link) download.Status = "failed" - if updateErr := l.repository.Update(download); updateErr != nil { + if updateErr := l.repository.Update(longRunningCtx, download); updateErr != nil { panic(updateErr) } } else { diff --git a/api/internal/core/services/download/default/service.go b/api/internal/core/services/download/default/service.go index e8105b9..884defa 100644 --- a/api/internal/core/services/download/default/service.go +++ b/api/internal/core/services/download/default/service.go @@ -1,6 +1,7 @@ package _default import ( + "context" "downloader/internal/core/entities" "downloader/internal/core/ports/download_request" "downloader/internal/core/services/download" @@ -24,7 +25,7 @@ func NewLocalService(repository download_request.Repository, uuidGen uuid.Gen, b } } -func (l *localService) Schedule(link string) (*entities.Download, error) { +func (l *localService) Schedule(ctx context.Context, link string) (*entities.Download, error) { download, err := entities.NewDownload(link)(l.uuidGen) if err != nil { l.logger.Warn("Could not parse download") @@ -33,13 +34,13 @@ func (l *localService) Schedule(link string) (*entities.Download, error) { logger := l.logger.With("downloadId", download.ID) - persistedDownloadRequest, uploadErr := l.repository.Create(download) + persistedDownloadRequest, uploadErr := l.repository.Create(ctx, download) if uploadErr != nil { logger.Error("failed to insert download request") return nil, uploadErr } - err = l.BackgroundService.Run(persistedDownloadRequest) + err = l.BackgroundService.Run(ctx, persistedDownloadRequest) if err != nil { logger.Error("failed to run download request") return nil, err @@ -48,10 +49,10 @@ func (l *localService) Schedule(link string) (*entities.Download, error) { return persistedDownloadRequest, nil } -func (l *localService) Get(id string) (*entities.Download, error) { - return l.repository.GetById(id) +func (l *localService) Get(ctx context.Context, id string) (*entities.Download, error) { + return l.repository.GetById(ctx, id) } -func (l *localService) GetAll(active bool) ([]*entities.Download, error) { - return l.repository.Get(active) +func (l *localService) GetAll(ctx context.Context, active bool) ([]*entities.Download, error) { + return l.repository.Get(ctx, active) } diff --git a/api/internal/core/services/download/handlers/onDownloadEventHandler.go b/api/internal/core/services/download/handlers/onDownloadEventHandler.go index 819795c..391f905 100644 --- a/api/internal/core/services/download/handlers/onDownloadEventHandler.go +++ b/api/internal/core/services/download/handlers/onDownloadEventHandler.go @@ -1,6 +1,7 @@ package handlers import ( + "context" "downloader/internal/core/ports/download_request" "downloader/internal/core/ports/downloadhandler" "fmt" @@ -20,7 +21,7 @@ func New(repository download_request.Repository, logger *zap.SugaredLogger) down } func (o *onDownloadEventHandler) OnTickEvent(downloadId string, progress string) { - download, err := o.repository.GetById(downloadId) + download, err := o.repository.GetById(context.TODO(), downloadId) if err != nil { o.logger.Warnw("could not finish updating progress as not download id available", "downloadId", downloadId, @@ -28,5 +29,5 @@ func (o *onDownloadEventHandler) OnTickEvent(downloadId string, progress string) return } download.Status = fmt.Sprintf("in-progress: %s", progress) - _ = o.repository.Update(download) + _ = o.repository.Update(context.TODO(), download) } diff --git a/api/internal/core/services/download/service.go b/api/internal/core/services/download/service.go index 5f12af1..86857e8 100644 --- a/api/internal/core/services/download/service.go +++ b/api/internal/core/services/download/service.go @@ -1,9 +1,12 @@ package download -import "downloader/internal/core/entities" +import ( + "context" + "downloader/internal/core/entities" +) type Service interface { - Schedule(link string) (*entities.Download, error) - Get(id string) (*entities.Download, error) - GetAll(active bool) ([]*entities.Download, error) + Schedule(ctx context.Context, link string) (*entities.Download, error) + Get(ctx context.Context, id string) (*entities.Download, error) + GetAll(ctx context.Context, active bool) ([]*entities.Download, error) } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3d8449f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3" + +services: + db: + image: postgres + restart: always + volumes: + - db_data:/var/lib/postgresql/data/pgdata + ports: + - 5432:5432 + environment: + PGDATA: /var/lib/postgresql/data/pgdata + POSTGRES_USER: downloader + POSTGRES_PASSWORD: downloadersecret + POSTGRES_DB: downloader + +volumes: + db_data: {}