diff --git a/.hooks/post-create b/.hooks/post-create new file mode 100755 index 0000000..f25d9d5 --- /dev/null +++ b/.hooks/post-create @@ -0,0 +1,8 @@ +#!/bin/bash + +id="$TUS_ID" +offset="$TUS_OFFSET" +size="$TUS_SIZE" + +echo "Upload created with ID ${id} and size ${size}" +cat /dev/stdin | jq . diff --git a/cmd/tusd/cli/hooks.go b/cmd/tusd/cli/hooks.go index 602636f..3c64afe 100644 --- a/cmd/tusd/cli/hooks.go +++ b/cmd/tusd/cli/hooks.go @@ -22,6 +22,7 @@ const ( HookPostFinish HookType = "post-finish" HookPostTerminate HookType = "post-terminate" HookPostReceive HookType = "post-receive" + HookPostCreate HookType = "post-create" HookPreCreate HookType = "pre-create" ) @@ -52,6 +53,8 @@ func SetupPostHooks(handler *tusd.Handler) { invokeHook(HookPostTerminate, info) case info := <-handler.UploadProgress: invokeHook(HookPostReceive, info) + case info := <-handler.CreatedUploads: + invokeHook(HookPostCreate, info) } } }() diff --git a/cmd/tusd/cli/serve.go b/cmd/tusd/cli/serve.go index 594d66e..e01b9d7 100644 --- a/cmd/tusd/cli/serve.go +++ b/cmd/tusd/cli/serve.go @@ -18,6 +18,7 @@ func Serve() { NotifyCompleteUploads: true, NotifyTerminatedUploads: true, NotifyUploadProgress: true, + NotifyCreatedUploads: true, }) if err != nil { stderr.Fatalf("Unable to create handler: %s", err) diff --git a/config.go b/config.go index 94b4f7e..9c8d454 100644 --- a/config.go +++ b/config.go @@ -34,6 +34,9 @@ type Config struct { // NotifyUploadProgress indicates whether sending notifications about // the upload progress using the UploadProgress channel should be enabled. NotifyUploadProgress bool + // NotifyCreatedUploads indicates whether sending notifications about + // the upload having been created using the CreatedUploads channel should be enabled. + NotifyCreatedUploads bool // Logger is the logger to use internally, mostly for printing requests. Logger *log.Logger // Respect the X-Forwarded-Host, X-Forwarded-Proto and Forwarded headers diff --git a/docs/hooks.md b/docs/hooks.md index 67d60f4..cfa3e47 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -19,6 +19,10 @@ On the other hand, there are a few *blocking* hooks, such as caused by the `pre- This event will be triggered before an upload is created, allowing you to run certain routines. For example, validating that specific metadata values are set, or verifying that a corresponding entity belonging to the upload (e.g. a user) exists. Because this event will result in a blocking hook, you can determine whether the upload should be created or rejected using the exit code. An exit code of `0` will allow the upload to be created and continued as usual. A non-zero exit code will reject an upload creation request, making it a good place for authentication and authorization. Please be aware, that during this stage the upload ID will be an empty string as the entity has not been created and therefore this piece of information is not yet available. +### post-create + +This event will be triggered after an upload is created, allowing you to run certain routines. For example, notifying other parts of your system that a new upload has to be handled. At this point the upload may have received some data already since the invocation of these hooks may be delayed by a short duration. + ### post-finish This event will be triggered after an upload is fully finished, meaning that all chunks have been transfered and saved in the storage. After this point, no further modifications, except possible deletion, can be made to the upload entity and it may be desirable to use the file for further processing or notify other applications of the completions of this upload. diff --git a/post_test.go b/post_test.go index 605ad7c..b7ffcc7 100644 --- a/post_test.go +++ b/post_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" . "github.com/tus/tusd" ) @@ -22,10 +23,14 @@ func TestPost(t *testing.T) { }).Return("foo", nil) handler, _ := NewHandler(Config{ - DataStore: store, - BasePath: "https://buy.art/files/", + DataStore: store, + BasePath: "https://buy.art/files/", + NotifyCreatedUploads: true, }) + c := make(chan FileInfo, 1) + handler.CreatedUploads = c + (&httpTest{ Method: "POST", ReqHeader: map[string]string{ @@ -39,6 +44,12 @@ func TestPost(t *testing.T) { "Location": "https://buy.art/files/foo", }, }).Run(handler, t) + + info := <-c + + a := assert.New(t) + a.Equal("foo", info.ID) + a.Equal(int64(300), info.Size) }) SubTest(t, "CreateExceedingMaxSizeFail", func(t *testing.T, store *MockFullDataStore) { diff --git a/unrouted_handler.go b/unrouted_handler.go index 0cd6c6a..2285e58 100644 --- a/unrouted_handler.go +++ b/unrouted_handler.go @@ -94,6 +94,12 @@ type UnroutedHandler struct { // happen if the NotifyUploadProgress field is set to true in the Config // structure. UploadProgress chan FileInfo + // CreatedUploads is used to send notifications about the uploads having been + // created. It triggers post creation and therefore has all the FileInfo incl. + // the ID available already. It facilitates the post-create hook. Sending to + // this channel will only happen if the NotifyCreatedUploads field is set to + // true in the Config structure. + CreatedUploads chan FileInfo // Metrics provides numbers of the usage for this handler. Metrics Metrics } @@ -124,6 +130,7 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) { CompleteUploads: make(chan FileInfo), TerminatedUploads: make(chan FileInfo), UploadProgress: make(chan FileInfo), + CreatedUploads: make(chan FileInfo), logger: config.Logger, extensions: extensions, Metrics: newMetrics(), @@ -284,6 +291,11 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) go handler.Metrics.incUploadsCreated() handler.log("UploadCreated", "id", id, "size", i64toa(size), "url", url) + if handler.config.NotifyCreatedUploads { + info.ID = id + handler.CreatedUploads <- info + } + if isFinal { if err := handler.composer.Concater.ConcatUploads(id, partialUploads); err != nil { handler.sendError(w, r, err)