This commit is contained in:
Kasper Juul Hermansen 2021-12-22 20:21:24 +01:00
parent a24d39d657
commit a8bd48e09f
Signed by: kjuulh
GPG Key ID: 0F95C140730F2F23
17 changed files with 344 additions and 31 deletions

12
api/.idea/dataSources.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="PostgreSQL - downloader@localhost" uuid="3a004c13-c9cf-4f31-9be7-1294e186d182">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/downloader</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -3,13 +3,27 @@ module downloader
go 1.17 go 1.17
require ( require (
github.com/fatih/color v1.13.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-chi/chi v1.5.4 // indirect github.com/go-chi/chi v1.5.4 // indirect
github.com/go-chi/render v1.0.1 // indirect github.com/go-chi/render v1.0.1 // indirect
github.com/google/uuid v1.3.0 // 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/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/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // 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 gopkg.in/yaml.v2 v2.4.0 // indirect
mellium.im/sasl v0.2.1 // indirect
) )

View File

@ -1,6 +1,8 @@
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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.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/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 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= 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/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 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/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 h1:VgraYqXx35W3ESBuGBzKdaa23FOVjJ7u1aY/zjeYcZw=
github.com/ovh/configstore v0.5.2/go.mod h1:krvoiDcrESKjEOz6AnKD8EbCmqHHQyOzEJNhAIhB4+Y= github.com/ovh/configstore v0.5.2/go.mod h1:krvoiDcrESKjEOz6AnKD8EbCmqHHQyOzEJNhAIhB4+Y=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/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= 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 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 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/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 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-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-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/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/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-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-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-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-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-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/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-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-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-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-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-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/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.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.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-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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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.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-20200313102051-9f266ea9e77c/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.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=

View File

@ -29,13 +29,14 @@ func (dr *requestDownloadRequest) Bind(r *http.Request) error {
} }
func (a *api) requestDownload(w http.ResponseWriter, r *http.Request) { func (a *api) requestDownload(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
data := &requestDownloadRequest{} data := &requestDownloadRequest{}
if err := render.Bind(r, data); err != nil { if err := render.Bind(r, data); err != nil {
_ = render.Render(w, r, responses.ErrInvalidRequest(err)) _ = render.Render(w, r, responses.ErrInvalidRequest(err))
return return
} }
download, err := a.drService.Schedule(data.Link) download, err := a.drService.Schedule(ctx, data.Link)
if err != nil { if err != nil {
_ = render.Render(w, r, responses.ErrInvalidRequest(err)) _ = render.Render(w, r, responses.ErrInvalidRequest(err))
return 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) { func (a *api) getDownloads(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()
active := request.URL.Query().Get("active") == "true" active := request.URL.Query().Get("active") == "true"
downloads, err := a.drService.GetAll(active) downloads, err := a.drService.GetAll(ctx, active)
if err != nil { if err != nil {
_ = render.Render(writer, request, responses.ErrInvalidRequest(err)) _ = render.Render(writer, request, responses.ErrInvalidRequest(err))
return 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) { 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 { if err != nil {
_ = render.Render(w, r, responses.ErrNotFound()) _ = render.Render(w, r, responses.ErrNotFound())
return return
@ -80,9 +83,14 @@ func newRequestDownloadResponse(download *entities.Download) *requestDownloadRes
} }
func newDownloadsResponse(downloads []*entities.Download) []render.Renderer { func newDownloadsResponse(downloads []*entities.Download) []render.Renderer {
list := []render.Renderer{} var list []render.Renderer
for _, download := range downloads { for _, download := range downloads {
list = append(list, newRequestDownloadResponse(download)) list = append(list, newRequestDownloadResponse(download))
} }
if len(list) == 0 {
return []render.Renderer{}
}
return list return list
} }

View File

@ -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
}

View File

@ -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
})
}

View File

@ -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)
}
}

View File

@ -3,7 +3,8 @@ package router
import ( import (
"downloader/internal/app/api/download" "downloader/internal/app/api/download"
"downloader/internal/app/infrastructure/logger" "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/downloadhandler"
"downloader/internal/core/ports/filehandler/mover/local" "downloader/internal/core/ports/filehandler/mover/local"
"downloader/internal/core/ports/fileorchestrator" "downloader/internal/core/ports/fileorchestrator"
@ -64,7 +65,9 @@ func setupDownloadRoute(router *router) {
destinationHandler := destinationhandler.New(mover) destinationHandler := destinationhandler.New(mover)
fileOrchestrator := fileorchestrator.New(newLogger, sourceHandler, destinationHandler) 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.NewYoutubeDlDownloader(newLogger)
dlHandler := downloadhandler.NewYtDlpDownloader(newLogger) dlHandler := downloadhandler.NewYtDlpDownloader(newLogger)
ondlHandler := handlers.New(drRepository, newLogger) ondlHandler := handlers.New(drRepository, newLogger)

View File

@ -1,10 +1,13 @@
package download_request package download_request
import "downloader/internal/core/entities" import (
"context"
"downloader/internal/core/entities"
)
type Repository interface { type Repository interface {
Create(download *entities.Download) (*entities.Download, error) Create(ctx context.Context, download *entities.Download) (*entities.Download, error)
GetById(id string) (*entities.Download, error) GetById(ctx context.Context, id string) (*entities.Download, error)
Update(download *entities.Download) error Update(ctx context.Context, download *entities.Download) error
Get(active bool) ([]*entities.Download, error) Get(ctx context.Context, active bool) ([]*entities.Download, error)
} }

View File

@ -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
}

View File

@ -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
}

View File

@ -1,7 +1,10 @@
package download package download
import "downloader/internal/core/entities" import (
"context"
"downloader/internal/core/entities"
)
type BackgroundService interface { type BackgroundService interface {
Run(download *entities.Download) error Run(ctx context.Context, download *entities.Download) error
} }

View File

@ -1,6 +1,7 @@
package _default package _default
import ( import (
"context"
"downloader/internal/core/entities" "downloader/internal/core/entities"
"downloader/internal/core/ports/download_request" "downloader/internal/core/ports/download_request"
"downloader/internal/core/services/download" "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} 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) logger := l.logger.With("downloadId", download.ID)
go func() { go func() {
longRunningCtx := context.TODO()
download.Status = "started" download.Status = "started"
_ = l.repository.Update(download) _ = l.repository.Update(longRunningCtx, download)
err := l.downloader.Download(download.Link, download.ID) err := l.downloader.Download(download.Link, download.ID)
download.Status = "done" download.Status = "done"
@ -33,13 +35,13 @@ func (l localBackgroundService) Run(download *entities.Download) error {
download.Status = "failed" download.Status = "failed"
} }
err = l.repository.Update(download) err = l.repository.Update(longRunningCtx, download)
if err != nil { if err != nil {
logger.Errorw("download request failed", logger.Errorw("download request failed",
"downloadLink", download.Link) "downloadLink", download.Link)
download.Status = "failed" download.Status = "failed"
if updateErr := l.repository.Update(download); updateErr != nil { if updateErr := l.repository.Update(longRunningCtx, download); updateErr != nil {
panic(updateErr) panic(updateErr)
} }
} else { } else {

View File

@ -1,6 +1,7 @@
package _default package _default
import ( import (
"context"
"downloader/internal/core/entities" "downloader/internal/core/entities"
"downloader/internal/core/ports/download_request" "downloader/internal/core/ports/download_request"
"downloader/internal/core/services/download" "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) download, err := entities.NewDownload(link)(l.uuidGen)
if err != nil { if err != nil {
l.logger.Warn("Could not parse download") 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) logger := l.logger.With("downloadId", download.ID)
persistedDownloadRequest, uploadErr := l.repository.Create(download) persistedDownloadRequest, uploadErr := l.repository.Create(ctx, download)
if uploadErr != nil { if uploadErr != nil {
logger.Error("failed to insert download request") logger.Error("failed to insert download request")
return nil, uploadErr return nil, uploadErr
} }
err = l.BackgroundService.Run(persistedDownloadRequest) err = l.BackgroundService.Run(ctx, persistedDownloadRequest)
if err != nil { if err != nil {
logger.Error("failed to run download request") logger.Error("failed to run download request")
return nil, err return nil, err
@ -48,10 +49,10 @@ func (l *localService) Schedule(link string) (*entities.Download, error) {
return persistedDownloadRequest, nil return persistedDownloadRequest, nil
} }
func (l *localService) Get(id string) (*entities.Download, error) { func (l *localService) Get(ctx context.Context, id string) (*entities.Download, error) {
return l.repository.GetById(id) return l.repository.GetById(ctx, id)
} }
func (l *localService) GetAll(active bool) ([]*entities.Download, error) { func (l *localService) GetAll(ctx context.Context, active bool) ([]*entities.Download, error) {
return l.repository.Get(active) return l.repository.Get(ctx, active)
} }

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"context"
"downloader/internal/core/ports/download_request" "downloader/internal/core/ports/download_request"
"downloader/internal/core/ports/downloadhandler" "downloader/internal/core/ports/downloadhandler"
"fmt" "fmt"
@ -20,7 +21,7 @@ func New(repository download_request.Repository, logger *zap.SugaredLogger) down
} }
func (o *onDownloadEventHandler) OnTickEvent(downloadId string, progress string) { 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 { if err != nil {
o.logger.Warnw("could not finish updating progress as not download id available", o.logger.Warnw("could not finish updating progress as not download id available",
"downloadId", downloadId, "downloadId", downloadId,
@ -28,5 +29,5 @@ func (o *onDownloadEventHandler) OnTickEvent(downloadId string, progress string)
return return
} }
download.Status = fmt.Sprintf("in-progress: %s", progress) download.Status = fmt.Sprintf("in-progress: %s", progress)
_ = o.repository.Update(download) _ = o.repository.Update(context.TODO(), download)
} }

View File

@ -1,9 +1,12 @@
package download package download
import "downloader/internal/core/entities" import (
"context"
"downloader/internal/core/entities"
)
type Service interface { type Service interface {
Schedule(link string) (*entities.Download, error) Schedule(ctx context.Context, link string) (*entities.Download, error)
Get(id string) (*entities.Download, error) Get(ctx context.Context, id string) (*entities.Download, error)
GetAll(active bool) ([]*entities.Download, error) GetAll(ctx context.Context, active bool) ([]*entities.Download, error)
} }

18
docker-compose.yml Normal file
View File

@ -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: {}