diff --git a/api/.idea/.gitignore b/api/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/api/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/api/.idea/api.iml b/api/.idea/api.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/api/.idea/api.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api/.idea/modules.xml b/api/.idea/modules.xml
new file mode 100644
index 0000000..d50cf45
--- /dev/null
+++ b/api/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api/.idea/vcs.xml b/api/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/api/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api/go.mod b/api/go.mod
index fe0a60a..a838522 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -5,4 +5,5 @@ go 1.17
require (
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
)
diff --git a/api/go.sum b/api/go.sum
index 544024a..7606d92 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -2,3 +2,5 @@ github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
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=
diff --git a/api/internal/app/api/common/responses/responses.go b/api/internal/app/api/common/responses/responses.go
index 0a082b1..cf622bf 100644
--- a/api/internal/app/api/common/responses/responses.go
+++ b/api/internal/app/api/common/responses/responses.go
@@ -27,3 +27,7 @@ func ErrInvalidRequest(err error) render.Renderer {
ErrorText: err.Error(),
}
}
+
+func ErrNotFound() render.Renderer {
+ return &ErrResponse{HTTPStatusCode: 404, StatusText: "Resource not found."}
+}
diff --git a/api/internal/app/api/download/download.go b/api/internal/app/api/download/download.go
deleted file mode 100644
index 5b3a4f0..0000000
--- a/api/internal/app/api/download/download.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package download
-
-import (
- "downloader/internal/app/api/common/responses"
- "errors"
- "github.com/go-chi/chi"
- "github.com/go-chi/render"
- "net/http"
-)
-
-type api struct{}
-
-func New() *api {
- return &api{}
-}
-
-func (api *api) SetupDownloadApi(router *chi.Mux) {
- router.Route("/downloads", func(r chi.Router) {
- r.Post("/", api.requestDownload)
- })
-}
-
-type requestDownloadRequest struct {
- Provider string `json:"provider"`
- Link string `json:"link"`
-}
-
-func (dr *requestDownloadRequest) Bind(r *http.Request) error {
- if dr.Link == "" || dr.Provider == "" {
- return errors.New("missing required download request field")
- }
-
- return nil
-}
-
-func (api *api) requestDownload(w http.ResponseWriter, r *http.Request) {
- data := &requestDownloadRequest{}
- if err := render.Bind(r, data); err != nil {
- _ = render.Render(w, r, responses.ErrInvalidRequest(err))
- return
- }
-}
diff --git a/api/internal/app/api/download/download_context.go b/api/internal/app/api/download/download_context.go
new file mode 100644
index 0000000..7737f52
--- /dev/null
+++ b/api/internal/app/api/download/download_context.go
@@ -0,0 +1,22 @@
+package download
+
+import (
+ "context"
+ "downloader/internal/app/api/common/responses"
+ "github.com/go-chi/chi"
+ "github.com/go-chi/render"
+ "net/http"
+)
+
+func Context(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if downloadId := chi.URLParam(r, "downloadId"); downloadId != "" {
+ ctx := context.WithValue(r.Context(), "downloadId", downloadId)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ } else {
+ _ = render.Render(w, r, responses.ErrNotFound())
+ return
+ }
+
+ })
+}
diff --git a/api/internal/app/api/download/request_download.go b/api/internal/app/api/download/request_download.go
new file mode 100644
index 0000000..a5b5123
--- /dev/null
+++ b/api/internal/app/api/download/request_download.go
@@ -0,0 +1,66 @@
+package download
+
+import (
+ "downloader/internal/app/api/common/responses"
+ "downloader/internal/core/entities"
+ "errors"
+ "github.com/go-chi/render"
+ "net/http"
+)
+
+type requestDownloadRequest struct {
+ Provider string `json:"provider"`
+ Link string `json:"link"`
+}
+
+type requestDownloadResponse struct {
+ *entities.Download
+}
+
+func (_ requestDownloadResponse) Render(_ http.ResponseWriter, _ *http.Request) error {
+ return nil
+}
+
+func (dr *requestDownloadRequest) Bind(r *http.Request) error {
+ if dr.Link == "" || dr.Provider == "" {
+ return errors.New("missing required download request field")
+ }
+
+ return nil
+}
+
+func (a *api) requestDownload(w http.ResponseWriter, r *http.Request) {
+ data := &requestDownloadRequest{}
+ if err := render.Bind(r, data); err != nil {
+ _ = render.Render(w, r, responses.ErrInvalidRequest(err))
+ return
+ }
+
+ download, err := a.drService.Schedule(data.Provider, data.Link)
+ if err != nil {
+ _ = render.Render(w, r, responses.ErrInvalidRequest(err))
+ return
+ }
+
+ render.Status(r, http.StatusAccepted)
+ _ = render.Render(w, r, newRequestDownloadResponse(download))
+}
+
+func (a *api) getDownloadById(w http.ResponseWriter, r *http.Request) {
+ downloadId := r.Context().Value("downloadId").(string)
+
+ download, err := a.drService.Get(downloadId)
+ if err != nil {
+ _ = render.Render(w, r, responses.ErrNotFound())
+ return
+ }
+
+ if err := render.Render(w, r, newRequestDownloadResponse(download)); err != nil {
+ _ = render.Render(w, r, responses.ErrInvalidRequest(err))
+ return
+ }
+}
+
+func newRequestDownloadResponse(download *entities.Download) *requestDownloadResponse {
+ return &requestDownloadResponse{Download: download}
+}
diff --git a/api/internal/app/api/download/routes.go b/api/internal/app/api/download/routes.go
new file mode 100644
index 0000000..f68ba63
--- /dev/null
+++ b/api/internal/app/api/download/routes.go
@@ -0,0 +1,24 @@
+package download
+
+import (
+ "downloader/internal/core/ports/download_request"
+ "github.com/go-chi/chi"
+)
+
+type api struct {
+ drService download_request.Service
+}
+
+func New(service download_request.Service) *api {
+ return &api{drService: service}
+}
+
+func (a *api) SetupDownloadApi(router *chi.Mux) {
+ router.Route("/downloads", func(r chi.Router) {
+ r.Post("/", a.requestDownload)
+ r.Route("/{downloadId}", func(r chi.Router) {
+ r.Use(Context)
+ r.Get("/", a.getDownloadById)
+ })
+ })
+}
diff --git a/api/internal/app/router/router.go b/api/internal/app/router/router.go
index 84c3115..4094aa6 100644
--- a/api/internal/app/router/router.go
+++ b/api/internal/app/router/router.go
@@ -2,6 +2,8 @@ package router
import (
"downloader/internal/app/api/download"
+ "downloader/internal/core/ports/download_request"
+ "downloader/pkg/common/uuid"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"net/http"
@@ -23,7 +25,7 @@ func NewRouter() *router {
}
func (router *router) Run() {
- http.ListenAndServe(":3333", router.internalRouter)
+ _ = http.ListenAndServe(":3333", router.internalRouter)
}
func (router *router) RegisterApi() *chi.Mux {
@@ -41,9 +43,17 @@ func (router *router) setupMiddleware() *router {
}
func (router *router) setupRoutes() *router {
- downloadApi := download.New()
-
- downloadApi.SetupDownloadApi(router.internalRouter)
+ setupDownloadRoute(router)
return router
}
+
+func setupDownloadRoute(router *router) {
+ drRepository := download_request.NewInMemoryRepository()
+ drBackgroundService := download_request.NewLocalBackgroundService(drRepository)
+ gen := uuid.New()
+
+ drService := download_request.NewLocalService(drRepository, gen, drBackgroundService)
+ downloadApi := download.New(drService)
+ downloadApi.SetupDownloadApi(router.internalRouter)
+}
diff --git a/api/internal/core/entities/Download.go b/api/internal/core/entities/Download.go
deleted file mode 100644
index 103437c..0000000
--- a/api/internal/core/entities/Download.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package entities
-
-type Download struct {
- ID string `json:"id"`
- Status string `json:"status"`
- Link string `json:"link"`
- Data string `json:"data"`
-}
diff --git a/api/internal/core/entities/download.go b/api/internal/core/entities/download.go
new file mode 100644
index 0000000..d65980a
--- /dev/null
+++ b/api/internal/core/entities/download.go
@@ -0,0 +1,28 @@
+package entities
+
+import (
+ "downloader/pkg/common/uuid"
+ "errors"
+)
+
+type Download struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ Link string `json:"link"`
+ Data string `json:"data"`
+}
+
+func NewDownload(link string, data string) func(uuidGen uuid.Gen) (*Download, error) {
+ return func(uuidGen uuid.Gen) (*Download, error) {
+ if link == "" || data == "" {
+ return nil, errors.New("A field was not valid")
+ }
+
+ return &Download{
+ ID: uuidGen.Create(),
+ Status: "scheduled",
+ Link: link,
+ Data: data,
+ }, nil
+ }
+}
diff --git a/api/internal/core/ports/download_request/download_request.go b/api/internal/core/ports/download_request/download_request.go
index cba72de..c0cd802 100644
--- a/api/internal/core/ports/download_request/download_request.go
+++ b/api/internal/core/ports/download_request/download_request.go
@@ -3,11 +3,16 @@ package download_request
import "downloader/internal/core/entities"
type Service interface {
- Schedule(provider string, link string) (entities.Download, error)
- Get(id string) (entities.Download, error)
+ Schedule(provider string, link string) (*entities.Download, error)
+ Get(id string) (*entities.Download, error)
}
type Repository interface {
- Create(download entities.Download) (entities.Download, error)
- GetById(id string) (entities.Download, error)
+ Create(download *entities.Download) (*entities.Download, error)
+ GetById(id string) (*entities.Download, error)
+ Update(download *entities.Download) error
+}
+
+type BackgroundService interface {
+ Run(download *entities.Download) error
}
diff --git a/api/internal/core/ports/download_request/in_memory.go b/api/internal/core/ports/download_request/in_memory.go
new file mode 100644
index 0000000..c440376
--- /dev/null
+++ b/api/internal/core/ports/download_request/in_memory.go
@@ -0,0 +1,110 @@
+package download_request
+
+import (
+ "downloader/internal/core/entities"
+ "downloader/pkg/common/uuid"
+ "errors"
+ "fmt"
+ "time"
+)
+
+// Repository
+type inMemoryRepository struct {
+ collection map[string]*entities.Download
+}
+
+func NewInMemoryRepository() Repository {
+ return &inMemoryRepository{collection: make(map[string]*entities.Download)}
+}
+
+func (i *inMemoryRepository) Create(download *entities.Download) (*entities.Download, error) {
+ if doc := i.collection[download.ID]; doc != nil {
+ return nil, errors.New("download request already exists")
+ }
+
+ i.collection[download.ID] = download
+
+ return download, nil
+}
+
+func (i *inMemoryRepository) Update(download *entities.Download) error {
+
+ if doc := i.collection[download.ID]; doc == nil {
+ return errors.New("download request doesn't exist exists")
+ }
+
+ i.collection[download.ID] = download
+
+ return nil
+}
+
+func (i *inMemoryRepository) GetById(id string) (*entities.Download, error) {
+ if download := i.collection[id]; download != nil {
+ return download, nil
+ } else {
+ return nil, errors.New("download was not found in the database")
+ }
+}
+
+// Service
+type localService struct {
+ repository Repository
+ uuidGen uuid.Gen
+ BackgroundService BackgroundService
+}
+
+func NewLocalService(repository Repository, uuidGen uuid.Gen, backgroundService BackgroundService) Service {
+ return &localService{
+ repository: repository,
+ uuidGen: uuidGen,
+ BackgroundService: backgroundService,
+ }
+}
+
+func (l *localService) Schedule(provider string, link string) (*entities.Download, error) {
+ download, err := entities.NewDownload(link, provider)(l.uuidGen)
+ if err != nil {
+ return nil, err
+ }
+
+ persistedDownloadRequest, uploadErr := l.repository.Create(download)
+ if uploadErr != nil {
+ return nil, uploadErr
+ }
+
+ err = l.BackgroundService.Run(persistedDownloadRequest)
+ if err != nil {
+ return nil, err
+ }
+
+ return persistedDownloadRequest, nil
+}
+
+func (l *localService) Get(id string) (*entities.Download, error) {
+ return l.repository.GetById(id)
+}
+
+// Background service
+type localBackgroundService struct {
+ repository Repository
+}
+
+func NewLocalBackgroundService(repository Repository) BackgroundService {
+ return &localBackgroundService{repository: repository}
+}
+
+func (l localBackgroundService) Run(download *entities.Download) error {
+ go func() {
+ time.Sleep(time.Second * 5)
+ download.Status = "done"
+ err := l.repository.Update(download)
+ if err != nil {
+ fmt.Printf("Download request: %s failed\n", download.ID)
+ panic(err)
+ } else {
+ fmt.Printf("Download request: %s done\n", download.ID)
+ }
+ }()
+
+ return nil
+}
diff --git a/api/pkg/common/uuid/uuid.go b/api/pkg/common/uuid/uuid.go
new file mode 100644
index 0000000..4d81f71
--- /dev/null
+++ b/api/pkg/common/uuid/uuid.go
@@ -0,0 +1,18 @@
+package uuid
+
+import "github.com/google/uuid"
+
+type Gen interface {
+ Create() string
+}
+
+type uuidGen struct {
+}
+
+func New() *uuidGen {
+ return &uuidGen{}
+}
+
+func (u uuidGen) Create() string {
+ return uuid.New().String()
+}