Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Standard Go HTTP server (not Cloudflare Workers) serving as a temporary file upl
**Request flow (`handler.go`):**
1. Parse `Authorization` header + `project`/`projectId` from query params or `param-*` headers (query params take precedence). `project` accepts **either** a project **sid** (stable slug, e.g. `my-project`) **or** a numeric project ID; `projectId` is always the numeric ID. Because a sid always starts with a letter (api `ReValidSID`: `^[a-z][a-z0-9\-]*[^\-]$`), an all-digit `project` is unambiguously an ID, so `uploadHandler` routes it to `projectID` (an explicit `projectId` still wins). Both are relayed to `me.authorized`, which resolves `project` by sid and `projectId` by numeric ID.
2. Authorize via `checkAuth()` in `auth.go`
3. Parse TTL (1–7 days, default 1) and optional filename the same way
3. Parse TTL (1–365 days, default 1) and optional filename the same way
4. Generate a 24-char alphanumeric `fn` (`generateFilename`, rejection-sampled to stay unbiased)
5. Stream body to GCS with cache-control and optional content-disposition, keyed by `fn`
6. Insert metadata into PostgreSQL via `pgctx.Exec`
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ Endpoint: https://dropbox.deploys.app/
| Name | Type | Data Type | Description |
|-----------------|----------|-----------|---------------------------------|
| Authorization | required | string | Authorization token |
| Param-Ttl | optional | number | 1-7, default 1 |
| Param-Ttl | optional | number | 1-365, default 1 |
| Param-Project | required | string | Project sid (stable slug) or numeric project ID |
| Param-Filename | optional | string | Filename in Content-Disposition |

#### Query Parameters

| Name | Type | Data Type | Description |
|------------|----------|-----------|---------------------------------|
| ttl | optional | number | 1-7, default 1 |
| ttl | optional | number | 1-365, default 1 |
| project | required | string | Project sid (stable slug) **or** numeric project ID — an all-digit value is treated as the ID |
| projectId | optional | string | Numeric project ID (same as passing a numeric `project`) |
| filename | optional | string | Filename in Content-Disposition |
Expand Down Expand Up @@ -148,7 +148,7 @@ Two steps: **create** the URL, then **PUT** the file to it.
|---------------|--------|-----------------------------------------------------------------------------|
| `project` | string | Project sid **or** numeric project ID (all-digit ⇒ ID). Required. |
| `projectId` | string | Numeric project ID (same as a numeric `project`). |
| `ttl` | number | Download lifetime of the resulting file, 1–7 days (default 1). |
| `ttl` | number | Download lifetime of the resulting file, 1–365 days (default 1). |
| `filename` | string | Optional `Content-Disposition` filename on download. |
| `contentType` | string | Optional. When set, the PUT **must** send this exact `Content-Type`. |
| `minSize` | number | Optional lower bound in bytes (clamped to ≥ 1, so empty uploads are refused).|
Expand Down
2 changes: 1 addition & 1 deletion handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) {

ttlStr := firstNonEmpty(r.URL.Query().Get("ttl"), r.Header.Get("param-ttl"))
ttlDays, _ := strconv.Atoi(ttlStr)
if ttlDays < 1 || ttlDays > 7 {
if ttlDays < 1 || ttlDays > 365 {
ttlDays = 1
}

Expand Down
26 changes: 14 additions & 12 deletions handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,9 @@ func TestUpload_TTL(t *testing.T) {
{"1", 1},
{"3", 3},
{"7", 7},
{"8", 1},
{"8", 8},
{"365", 365},
{"366", 1},
{"-1", 1},
{"abc", 1},
{"", 1},
Expand Down Expand Up @@ -658,17 +660,17 @@ func TestUpload_ExplicitProjectIDWinsOverNumericProject(t *testing.T) {
func TestIsAllDigits(t *testing.T) {
t.Parallel()
cases := map[string]bool{
"": false,
"12345": true,
"0": true,
"acme": false,
"acme-prod": false,
"12a": false,
"a123": false,
" 12": false,
"12 ": false,
"-1": false,
"1.0": false,
"": false,
"12345": true,
"0": true,
"acme": false,
"acme-prod": false,
"12a": false,
"a123": false,
" 12": false,
"12 ": false,
"-1": false,
"1.0": false,
}
for in, want := range cases {
if got := isAllDigits(in); got != want {
Expand Down
4 changes: 2 additions & 2 deletions upload_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func parseUploadToken(key []byte, token string) (uploadGrant, bool) {
type uploadURLRequest struct {
Project string `json:"project"` // project sid or numeric id
ProjectID string `json:"projectId"` // numeric id (same as a numeric project)
TTL int `json:"ttl"` // file download lifetime, 1-7 days (default 1)
TTL int `json:"ttl"` // file download lifetime, 1-365 days (default 1)
Filename string `json:"filename"` // optional Content-Disposition on download
ContentType string `json:"contentType"` // optional; the PUT must send this exactly
MinSize int64 `json:"minSize"` // optional lower bound in bytes (>= 1)
Expand Down Expand Up @@ -161,7 +161,7 @@ func (a *App) uploadURLHandler(w http.ResponseWriter, r *http.Request) {
}

ttlDays := req.TTL
if ttlDays < 1 || ttlDays > 7 {
if ttlDays < 1 || ttlDays > 365 {
ttlDays = 1
}

Expand Down