diff --git a/README.md b/README.md index 62e4bb8..43895ef 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,83 @@ the `-4` respective the `-6` commandline flags. It does not support TLS at the moment. Use a nginx reverse proxy in front of it. +### Server REST API + +Every endpoint returns a JSON object. Each returned object contains the data requested plus: + +- success: true or false +- code: HTTP Response Code +- message: error message, if success==false + +#### Endpoints + +| HTTP Method | Endpoint | Parameters | Input | Returns | Description | +|-------------|-------------------|---------------------|----------------------------|---------------------------------------|-----------------------------------------------| +| GET | /v1/uploads | apicontext,q,expire | | List of upload objects | list upload objects | +| POST | /v1/uploads | | multipart-formdata file[s] | List of 1 upload object if successful | upload a file and create a new upload object | +| GET | /v1/uploads/{id} | | | List of 1 upload object if successful | list one specific upload object matching {id} | +| DELETE | /v1/uploads/{id} | | | Noting | delete an upload object identified by {id} | +| PUT | /v1/uploads/{id} | | JSON upload object | List of 1 upload object if successful | modify an upload object identified by {id} | +| GET | /v1/download/{id} | | | File download | Download the file associated with the object | +| GET | /v1/forms | apicontext,q,expire | | List of form objects | list form objects | +| POST | /v1/forms | | JSON form object | List of 1 form object if successful | create a new form object | +| GET | /v1/forms/{id} | | | List of 1 form object if successful | list one specific form object matching {id} | +| DELETE | /v1/forms/{id} | | | Noting | delete an form object identified by {id} | +| PUT | /v1/forms/{id} | | JSON form object | List of 1 form object if successful | modify an form object identified by {id} | + +#### Consumer URLs + +The following endpoints are no API urls, but accessed directly by consumers using their browser or `wget` etc: + +| URL | Description | +|-------------------------|---------------------------------------------------------| +| / | Display a short welcome message, can be customized | +| /download/{id}[/{file}] | Download link returned after an upload has been created | +| /form/{id} | Upload form for consumer | + +#### API Objects + +Response: + +| Field | Data Type | Description | +|---------|-----------|---------------------------------------| +| success | bool | if true the request was successful | +| code | int | HTTP response code | +| message | string | error message, if any | +| uploads | array | list of upload objects (may be empty) | +| forms | array | list of form objects (may be empty) | + +Upload: + +| Field | Data Type | Description | +|----------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| id | string | unique identifier for the object | +| expire | string | when the upload has to expire, either "asap" or a Duration using numbers and the letters d,h,m,s (days,hours,minutes,seconds), e.g. 2d4h30m | +| file | string | filename after uploading, this is what a consumer gets when downloading it | +| members | array of strings | list of the original filenames | +| uploaded | timestamp | time of object creation | +| context | string | the API context the upload has been created under | +| url | string | the download URL | + +Form: + +| Field | Data Type | Description | +|-------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------| +| id | string | unique identifier for the object | +| expire | string | when the form has to expire, either "asap" or a Duration using numbers and the letters d,h,m,s (days,hours,minutes,seconds), e.g. 2d4h30m | +| description | string | arbitrary description, shown on the form page | +| context | string | the API context the form has been created under and the uploaded files will be created on | +| notify | string | email address of the form creator, who gets an email once the consumer has uploaded files using the form | +| created | timestamp | time of object creation | +| url | string | the form URL | + +Note: if the expire field for a form is not set or set to "asap" only +1 upload object can be created from it. However, if a duration has +been specified, the form can be used multiple times and thus creates +multiple upload objects. + + + ## Client Usage ``` diff --git a/api/server.go b/api/server.go index 54a0a22..9a4aee8 100644 --- a/api/server.go +++ b/api/server.go @@ -56,33 +56,30 @@ func Runserver(conf *cfg.Config, args []string) error { api := router.Group(conf.ApiPrefix + ApiVersion) { // upload - api.Post("/file/", auth, func(c *fiber.Ctx) error { + api.Post("/uploads/", auth, func(c *fiber.Ctx) error { return FilePut(c, conf, db) }) - // download w/o expire - api.Get("/file/:id/:file", auth, func(c *fiber.Ctx) error { - return FileGet(c, conf, db) - }) - api.Get("/file/:id/", auth, func(c *fiber.Ctx) error { - return FileGet(c, conf, db) - }) - // remove - api.Delete("/file/:id/", auth, func(c *fiber.Ctx) error { + api.Delete("/uploads/:id/", auth, func(c *fiber.Ctx) error { err := DeleteUpload(c, conf, db) return SendResponse(c, "", err) }) // listing - api.Get("/list/", auth, func(c *fiber.Ctx) error { + api.Get("/uploads", auth, func(c *fiber.Ctx) error { return List(c, conf, db) }) - // info + // info/describe api.Get("/upload/:id/", auth, func(c *fiber.Ctx) error { return Describe(c, conf, db) }) + + // download w/o expire + api.Get("/download/:id/", auth, func(c *fiber.Ctx) error { + return FileGet(c, conf, db) + }) } // public routes diff --git a/upctl/lib/client.go b/upctl/lib/client.go index 7e9e08f..942ef2b 100644 --- a/upctl/lib/client.go +++ b/upctl/lib/client.go @@ -164,7 +164,7 @@ func HandleResponse(c *cfg.Config, resp *req.Response) error { func UploadFiles(w io.Writer, c *cfg.Config, args []string) error { // setup url, req.Request, timeout handling etc - rq := Setup(c, "/file/") + rq := Setup(c, "/uploads/") // collect files to upload from @argv if err := GatherFiles(rq, args); err != nil { @@ -220,7 +220,7 @@ func List(w io.Writer, c *cfg.Config, args []string) error { func Delete(w io.Writer, c *cfg.Config, args []string) error { for _, id := range args { - rq := Setup(c, "/file/"+id+"/") + rq := Setup(c, "/uploads/"+id+"/") resp, err := rq.R.Delete(rq.Url) @@ -266,7 +266,7 @@ func Download(w io.Writer, c *cfg.Config, args []string) error { id := args[0] - rq := Setup(c, "/file/"+id+"/") + rq := Setup(c, "/uploads/"+id+"/") if !c.Silent { // progres bar diff --git a/upctl/lib/client_test.go b/upctl/lib/client_test.go index 0a2e449..7f2e1be 100644 --- a/upctl/lib/client_test.go +++ b/upctl/lib/client_test.go @@ -111,7 +111,7 @@ func TestUploadFiles(t *testing.T) { name: "upload-file", apikey: "token", wantfail: false, - route: "/file/", + route: "/uploads/", sendcode: 200, sendjson: `{"success": true}`, files: []string{"../t/t1"}, // pwd is lib/ ! @@ -121,7 +121,7 @@ func TestUploadFiles(t *testing.T) { name: "upload-dir", apikey: "token", wantfail: false, - route: "/file/", + route: "/uploads/", sendcode: 200, sendjson: `{"success": true}`, files: []string{"../t"}, // pwd is lib/ ! @@ -131,7 +131,7 @@ func TestUploadFiles(t *testing.T) { name: "upload-catch-nonexistent-file", apikey: "token", wantfail: true, - route: "/file/", + route: "/uploads/", sendcode: 200, sendjson: `{"success": false}`, files: []string{"../t/none"}, @@ -141,7 +141,7 @@ func TestUploadFiles(t *testing.T) { name: "upload-catch-no-access", apikey: "token", wantfail: true, - route: "/file/", + route: "/uploads/", sendcode: 403, sendjson: `{"success": false}`, files: []string{"../t/t1"}, @@ -151,7 +151,7 @@ func TestUploadFiles(t *testing.T) { name: "upload-check-output", apikey: "token", wantfail: false, - route: "/file/", + route: "/uploads/", sendcode: 200, sendjson: `{"uploads":[ { @@ -313,7 +313,7 @@ func TestDelete(t *testing.T) { name: "delete", apikey: "token", wantfail: false, - route: "/file/", + route: "/uploads/", sendcode: 200, sendjson: `{"success":true,"message":"","code":200}`, files: []string{"cc2c965a"}, @@ -324,7 +324,7 @@ func TestDelete(t *testing.T) { name: "delete-catch-empty-json", apikey: "token", wantfail: true, - route: "/file/", + route: "/uploads/", sendcode: 200, sendjson: "", files: []string{"cc2c965a"}, @@ -334,7 +334,7 @@ func TestDelete(t *testing.T) { name: "delete-catch-no-access", apikey: "token", wantfail: true, - route: "/file/", + route: "/uploads/", sendcode: 503, sendjson: listingnoaccess, files: []string{"cc2c965a"}, @@ -365,7 +365,7 @@ func TestDownload(t *testing.T) { name: "download", apikey: "token", wantfail: false, - route: "/file/", + route: "/uploads/", sendcode: 200, sendfile: "../t/t1", files: []string{"cc2c965a"}, @@ -376,7 +376,7 @@ func TestDownload(t *testing.T) { name: "download-catch-empty-response", apikey: "token", wantfail: true, - route: "/file/", + route: "/uploads/", sendcode: 200, files: []string{"cc2c965a"}, method: "GET", @@ -385,7 +385,7 @@ func TestDownload(t *testing.T) { name: "download-catch-no-access", apikey: "token", wantfail: true, - route: "/file/", + route: "/uploads/", sendcode: 503, sendjson: listingnoaccess, files: []string{"cc2c965a"},