Skip to content

Commit a494776

Browse files
committed
Add ability to set / as a short link
1 parent f9d6278 commit a494776

3 files changed

Lines changed: 85 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,5 @@ If `WEB_UI=true` is set, a web interface is exposed on `http://localhost:8090` (
127127
* The app uses a BoltDB bucket named `urls` to store key → URL mappings.
128128

129129
* The autogenerated keys are 6 characters drawn from `a-zA-Z0-9`.
130+
131+
* `/` can be set as a short link as well and still function as the list URLs endpoint.

cmd/short-it/main.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var rybbitClient = &http.Client{Timeout: 3 * time.Second}
4040

4141
const bucketName = "urls"
4242
const maxPageSize = 100
43+
const rootRedirectKey = "_root"
4344

4445
const envAppToken = "APP_TOKEN"
4546
const envDBPath = "DB_PATH"
@@ -679,9 +680,30 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
679680
case http.MethodPost:
680681
authMiddleware(handleCreateShortURL)(w, r)
681682
case http.MethodGet:
682-
authMiddleware(handleListURLs)(w, r)
683+
if r.Header.Get("Authorization") == cfg.AppToken {
684+
handleListURLs(w, r)
685+
return
686+
}
687+
688+
url, err := getURL(rootRedirectKey)
689+
if err == nil {
690+
r.URL.Path = "/"
691+
handlePageView(r)
692+
http.Redirect(w, r, url, http.StatusFound)
693+
return
694+
}
695+
696+
writeJSONError(w, http.StatusUnauthorized, "Unauthorized: Invalid or missing Authorization header token.")
697+
case http.MethodPut:
698+
authMiddleware(func(w http.ResponseWriter, r *http.Request) {
699+
handlePutCustomURL(w, r, rootRedirectKey)
700+
})(w, r)
701+
case http.MethodDelete:
702+
authMiddleware(func(w http.ResponseWriter, r *http.Request) {
703+
handleDeleteURL(w, r, rootRedirectKey)
704+
})(w, r)
683705
default:
684-
writeJSONError(w, http.StatusMethodNotAllowed, fmt.Sprintf("Method not allowed: Unsupported HTTP method '%s' on root endpoint. Use POST to create or GET to list.", r.Method))
706+
writeJSONError(w, http.StatusMethodNotAllowed, fmt.Sprintf("Method not allowed: Unsupported HTTP method '%s' on root endpoint. Use POST to create, GET to list/redirect, PUT to set root redirect, or DELETE to remove it.", r.Method))
685707
}
686708
return
687709
}

cmd/short-it/main_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,4 +540,63 @@ func BenchmarkGetURL(b *testing.B) {
540540
for i := 0; i < b.N; i++ {
541541
getURL(testKey)
542542
}
543+
}
544+
545+
func TestRootRedirectLogic(t *testing.T) {
546+
testDB := setupTestDB(t)
547+
defer teardownTestDB(testDB, t)
548+
549+
originalDB := db
550+
originalConfig := cfg
551+
db = testDB
552+
defer func() {
553+
db = originalDB
554+
cfg = originalConfig
555+
}()
556+
557+
cfg.AppToken = "test-token"
558+
testTarget := "https://portfolio.example.com"
559+
560+
// 1. Test setting the root redirect via PUT /
561+
reqPut := httptest.NewRequest("PUT", "/", nil)
562+
reqPut.Header.Set("Authorization", "test-token")
563+
reqPut.Header.Set("URL", testTarget)
564+
wPut := httptest.NewRecorder()
565+
handleRequest(wPut, reqPut)
566+
567+
if wPut.Code != http.StatusCreated {
568+
t.Fatalf("Expected 201 Created for setting root redirect, got %d", wPut.Code)
569+
}
570+
571+
// 2. Test public access (GET / without auth) -> Should Redirect
572+
reqPublicGet := httptest.NewRequest("GET", "/", nil)
573+
wPublicGet := httptest.NewRecorder()
574+
handleRequest(wPublicGet, reqPublicGet)
575+
576+
if wPublicGet.Code != http.StatusFound {
577+
t.Fatalf("Expected 302 Found for public root request, got %d", wPublicGet.Code)
578+
}
579+
if loc := wPublicGet.Header().Get("Location"); loc != testTarget {
580+
t.Errorf("Expected redirect to %s, got %s", testTarget, loc)
581+
}
582+
583+
// 3. Test API access (GET / with auth) -> Should Return JSON List
584+
reqApiGet := httptest.NewRequest("GET", "/", nil)
585+
reqApiGet.Header.Set("Authorization", "test-token")
586+
wApiGet := httptest.NewRecorder()
587+
handleRequest(wApiGet, reqApiGet)
588+
589+
if wApiGet.Code != http.StatusOK {
590+
t.Fatalf("Expected 200 OK for API list request, got %d", wApiGet.Code)
591+
}
592+
593+
// 4. Test deleting the root redirect
594+
reqDel := httptest.NewRequest("DELETE", "/", nil)
595+
reqDel.Header.Set("Authorization", "test-token")
596+
wDel := httptest.NewRecorder()
597+
handleRequest(wDel, reqDel)
598+
599+
if wDel.Code != http.StatusNoContent {
600+
t.Fatalf("Expected 204 No Content for deleting root redirect, got %d", wDel.Code)
601+
}
543602
}

0 commit comments

Comments
 (0)