Skip to content

Commit a1067bb

Browse files
committed
Change env var from PORT to API_PORT, fixed some web UI configuration, added env var for public facing URLs
1 parent 266e16e commit a1067bb

4 files changed

Lines changed: 130 additions & 26 deletions

File tree

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Run locally:
1919

2020
```bash
2121
go build -o short-it ./cmd/short-it
22-
APP_TOKEN=your-secret-token DB_PATH=short-it.db PORT=8080 ./short-it
22+
APP_TOKEN=your-secret-token DB_PATH=short-it.db API_PORT=8080 ./short-it
2323
```
2424

2525
Run with Docker:
@@ -36,7 +36,7 @@ docker-compose up -d
3636
# edit docker-compose.yml to set APP_TOKEN or update the environment
3737
```
3838

39-
The server listens on `PORT` (default `8080`) and stores the BoltDB file at `DB_PATH` (default `short-it.db`).
39+
The server listens on `API_PORT` (default `8080`) and stores the BoltDB file at `DB_PATH` (default `short-it.db`).
4040

4141
Or deploy it in one click:
4242

@@ -47,19 +47,21 @@ Or deploy it in one click:
4747
Environment variables:
4848

4949
- `APP_TOKEN` (required): token used for authorization on protected endpoints
50-
- `PORT` (optional): HTTP port (default `8080`)
50+
- `API_PORT` (optional): HTTP port (default `8080`)
51+
- `API_URL` (optional): URL to access the API (default `http://localhost:8080`)
5152
- `DB_PATH` (optional): path to BoltDB file (default `short-it.db`)
5253
- `WEB_UI` (optional): set to `true` to host a small link-creation webpage
54+
- `WEB_UI_URL` (optional): URL to access the web UI (default `http://localhost:8090`)
5355
- `WEB_UI_PORT` (optional): port for the web UI server (default `8080`)
5456
- `RYBBIT_SITE_ID` (optional): Site ID provided by Rybbit
5557
- `RYBBIT_SITE_KEY` (optional): API key found in account settings for Rybbit
5658
- `RYBBIT_SITE_URL` (optional): Base URL for your Rybbit instance
5759

5860
Notes for `WEB_UI`:
5961

60-
- The API server still listens on `PORT`.
62+
- The API server still listens on `API_PORT`.
6163
- The web UI starts only when `WEB_UI=true`.
62-
- If `WEB_UI_PORT` is the same value as `PORT`, the UI is skipped to avoid interfering with the API process.
64+
- If `WEB_UI_PORT` is the same value as `API_PORT`, the UI is skipped to avoid interfering with the API process.
6365

6466
## HTTP API
6567

cmd/short-it/main.go

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,18 @@ const maxPageSize = 100
3333

3434
const envAppToken = "APP_TOKEN"
3535
const envDBPath = "DB_PATH"
36-
const envPort = "PORT"
36+
const envPort = "API_PORT"
37+
const envAPIBaseURL = "API_URL"
3738
const envWebUI = "WEB_UI"
39+
const envWebUIPath = "WEB_UI_URL"
3840
const envWebUIPort = "WEB_UI_PORT"
3941

4042
const envRybbitSiteID = "RYBBIT_SITE_ID"
4143
const envRybbitSiteKey = "RYBBIT_SITE_KEY"
4244
const envRybbitSiteURL = "RYBBIT_SITE_URL"
4345

4446
const defaultAPIPort = "8080"
45-
const defaultWebUIPort = "8080"
47+
const defaultWebUIPort = "8090"
4648
const defaultDBPath = "short-it.db"
4749

4850
const webUIPage = `<!doctype html>
@@ -118,6 +120,34 @@ func envOrDefault(key, fallback string) string {
118120
return v
119121
}
120122

123+
func normalizeBaseURL(raw string) string {
124+
return strings.TrimRight(strings.TrimSpace(raw), "/")
125+
}
126+
127+
func resolveAPIBaseURL(r *http.Request) string {
128+
if base := normalizeBaseURL(os.Getenv(envAPIBaseURL)); base != "" {
129+
return base
130+
}
131+
132+
host := r.Host
133+
if h, _, err := net.SplitHostPort(r.Host); err == nil {
134+
host = h
135+
}
136+
if host == "" {
137+
host = "localhost"
138+
}
139+
140+
return fmt.Sprintf("http://%s:%s", host, envOrDefault(envPort, defaultAPIPort))
141+
}
142+
143+
func resolveWebUIBaseURL(webUIPort string) string {
144+
if base := normalizeBaseURL(os.Getenv(envWebUIPath)); base != "" {
145+
return base
146+
}
147+
148+
return fmt.Sprintf("http://localhost:%s", webUIPort)
149+
}
150+
121151
func writeJSON(w http.ResponseWriter, status int, body any) {
122152
w.Header().Set("Content-Type", "application/json")
123153
w.WriteHeader(status)
@@ -528,17 +558,7 @@ func handleWebUICreate(w http.ResponseWriter, r *http.Request) {
528558
return
529559
}
530560

531-
host := r.Host
532-
if h, _, err := net.SplitHostPort(r.Host); err == nil {
533-
host = h
534-
}
535-
if host == "" {
536-
host = "localhost"
537-
}
538-
539-
apiPort := envOrDefault(envPort, defaultAPIPort)
540-
541-
shortURL := fmt.Sprintf("http://%s:%s/%s", host, apiPort, key)
561+
shortURL := fmt.Sprintf("%s/%s", resolveAPIBaseURL(r), key)
542562

543563
writeJSON(w, http.StatusOK, map[string]string{
544564
"key": key,
@@ -584,15 +604,19 @@ func main() {
584604
webUIMux.HandleFunc("/api/create", handleWebUICreate)
585605

586606
go func() {
587-
log.Printf("[web-ui] listening on :%s", webUIPort)
607+
log.Printf("[web-ui] listening on :%s (public %s)", webUIPort, resolveWebUIBaseURL(webUIPort))
588608
if err := http.ListenAndServe(":"+webUIPort, webUIMux); err != nil {
589609
log.Printf("[web-ui] server stopped: %v", err)
590610
}
591611
}()
592612
}
593613
}
594614

595-
log.Printf("[api] listening on :%s", port)
615+
apiBase := normalizeBaseURL(os.Getenv(envAPIBaseURL))
616+
if apiBase == "" {
617+
apiBase = fmt.Sprintf("http://localhost:%s", port)
618+
}
619+
log.Printf("[api] listening on :%s (public %s)", port, apiBase)
596620
log.Fatal(http.ListenAndServe(":"+port, apiMux))
597621
}
598622

cmd/short-it/main_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,82 @@ func TestDeleteURL(t *testing.T) {
267267
}
268268
}
269269

270+
func TestHandleWebUICreateUsesAPIURL(t *testing.T) {
271+
testDB := setupTestDB(t)
272+
defer teardownTestDB(testDB, t)
273+
274+
originalDB := db
275+
originalToken := authToken
276+
db = testDB
277+
authToken = "test-token"
278+
defer func() {
279+
db = originalDB
280+
authToken = originalToken
281+
}()
282+
283+
t.Setenv(envAPIBaseURL, "https://short.example.com")
284+
t.Setenv(envPort, "8080")
285+
286+
body := `{"token":"test-token","url":"https://example.com","custom_path":"blog"}`
287+
req := httptest.NewRequest(http.MethodPost, "/api/create", strings.NewReader(body))
288+
req.Header.Set("Content-Type", "application/json")
289+
req.Host = "web-link.example.com:8090"
290+
w := httptest.NewRecorder()
291+
292+
handleWebUICreate(w, req)
293+
294+
if w.Code != http.StatusOK {
295+
t.Fatalf("Expected 200, got %d", w.Code)
296+
}
297+
298+
var response map[string]string
299+
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
300+
t.Fatalf("Failed to decode response: %v", err)
301+
}
302+
303+
if got, want := response["short_url"], "https://short.example.com/blog"; got != want {
304+
t.Fatalf("Expected short_url %q, got %q", want, got)
305+
}
306+
}
307+
308+
func TestHandleWebUICreateFallsBackToHostAndAPIPort(t *testing.T) {
309+
testDB := setupTestDB(t)
310+
defer teardownTestDB(testDB, t)
311+
312+
originalDB := db
313+
originalToken := authToken
314+
db = testDB
315+
authToken = "test-token"
316+
defer func() {
317+
db = originalDB
318+
authToken = originalToken
319+
}()
320+
321+
t.Setenv(envAPIBaseURL, "")
322+
t.Setenv(envPort, "8080")
323+
324+
body := `{"token":"test-token","url":"https://example.com","custom_path":"blog"}`
325+
req := httptest.NewRequest(http.MethodPost, "/api/create", strings.NewReader(body))
326+
req.Header.Set("Content-Type", "application/json")
327+
req.Host = "web-link.example.com:8090"
328+
w := httptest.NewRecorder()
329+
330+
handleWebUICreate(w, req)
331+
332+
if w.Code != http.StatusOK {
333+
t.Fatalf("Expected 200, got %d", w.Code)
334+
}
335+
336+
var response map[string]string
337+
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
338+
t.Fatalf("Failed to decode response: %v", err)
339+
}
340+
341+
if got, want := response["short_url"], "http://web-link.example.com:8080/blog"; got != want {
342+
t.Fatalf("Expected short_url %q, got %q", want, got)
343+
}
344+
}
345+
270346
func TestListURLs(t *testing.T) {
271347
testDB := setupTestDB(t)
272348
defer teardownTestDB(testDB, t)

docker-compose.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,23 @@ version: '3.8'
22

33
services:
44
short-it:
5-
image: ghcr.io/auravoid/short-it:latest
6-
# build:
7-
# context: .
8-
# dockerfile: Dockerfile
5+
# image: ghcr.io/auravoid/short-it:latest
6+
build:
7+
context: .
8+
dockerfile: Dockerfile
99
ports:
1010
- "8080:8080"
1111
- "8090:8090"
1212
environment:
1313
#- RYBBIT_SITE_ID=YOUR_SITE_ID_HERE
1414
#- RYBBIT_SITE_KEY=YOUR_API_KEY_HERE
1515
#- RYBBIT_SITE_URL=https://your-rybbit-instance.example.com
16-
- WEB_UI=false
16+
- WEB_UI=true
1717
- WEB_UI_PORT=8090
18+
- WEB_UI_URL=http://localhost:8090
1819
- APP_TOKEN=YOUR_TOKEN_HERE
19-
- PORT=8080
20+
- API_PORT=8080
21+
- API_URL=http://localhost:8080
2022
- DB_PATH=/data/short-it.db
2123
volumes:
2224
- short-it-data:/data

0 commit comments

Comments
 (0)