Use url.1440.news for shorter URLs (28 chars vs 32)

- Changed short URL format from app.1440.news/r/{code} to url.1440.news/{code}
- Added Traefik routing for url.1440.news domain
- Root handler checks Host header to route url.1440.news requests
- Legacy /r/ path still supported for backwards compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
primal
2026-01-28 22:52:45 -05:00
parent 1ab45033cd
commit 283a221efd
3 changed files with 43 additions and 12 deletions
+33 -6
View File
@@ -240,11 +240,35 @@ func (c *Crawler) StartDashboard(addr string) error {
c.handleDashboard(w, r) c.handleDashboard(w, r)
}) })
// URL shortener redirect handler // URL shortener redirect handler (legacy /r/ path)
http.HandleFunc("/r/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/r/", func(w http.ResponseWriter, r *http.Request) {
c.handleRedirect(w, r) c.handleRedirect(w, r)
}) })
// Root handler for url.1440.news short URLs
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
host := r.Host
// Strip port if present
if idx := strings.Index(host, ":"); idx != -1 {
host = host[:idx]
}
// If this is url.1440.news, treat path as short code
if host == "url.1440.news" {
c.handleRedirect(w, r)
return
}
// Otherwise, redirect to dashboard for root path
if r.URL.Path == "/" {
http.Redirect(w, r, "/dashboard", http.StatusFound)
return
}
// Unknown path
http.NotFound(w, r)
})
http.HandleFunc("/api/stats", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/stats", func(w http.ResponseWriter, r *http.Request) {
c.handleAPIStats(w, r) c.handleAPIStats(w, r)
}) })
@@ -2235,15 +2259,18 @@ func (c *Crawler) handleAPIStats(w http.ResponseWriter, r *http.Request) {
} }
// handleRedirect handles short URL redirects // handleRedirect handles short URL redirects
// Supports both /r/{code} (legacy) and /{code} (for url.1440.news)
func (c *Crawler) handleRedirect(w http.ResponseWriter, r *http.Request) { func (c *Crawler) handleRedirect(w http.ResponseWriter, r *http.Request) {
// Extract code from path: /r/{code}
path := r.URL.Path path := r.URL.Path
if !strings.HasPrefix(path, "/r/") { var code string
http.NotFound(w, r)
return // Support both /r/{code} and /{code} formats
if strings.HasPrefix(path, "/r/") {
code = strings.TrimPrefix(path, "/r/")
} else {
code = strings.TrimPrefix(path, "/")
} }
code := strings.TrimPrefix(path, "/r/")
if code == "" { if code == "" {
http.NotFound(w, r) http.NotFound(w, r)
return return
+8 -4
View File
@@ -21,18 +21,22 @@ services:
- atproto - atproto
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
# Production: HTTPS with Let's Encrypt # Production: HTTPS with Let's Encrypt for app.1440.news
- "traefik.http.routers.app-1440-news.rule=Host(`app.1440.news`)" - "traefik.http.routers.app-1440-news.rule=Host(`app.1440.news`)"
- "traefik.http.routers.app-1440-news.entrypoints=https" - "traefik.http.routers.app-1440-news.entrypoints=https"
- "traefik.http.routers.app-1440-news.tls.certresolver=letsencrypt-dns" - "traefik.http.routers.app-1440-news.tls.certresolver=letsencrypt-dns"
# Production: HTTP to HTTPS redirect # Production: HTTPS for url.1440.news (URL shortener)
- "traefik.http.routers.app-1440-news-redirect.rule=Host(`app.1440.news`)" - "traefik.http.routers.url-1440-news.rule=Host(`url.1440.news`)"
- "traefik.http.routers.url-1440-news.entrypoints=https"
- "traefik.http.routers.url-1440-news.tls.certresolver=letsencrypt-dns"
# Production: HTTP to HTTPS redirect for both domains
- "traefik.http.routers.app-1440-news-redirect.rule=Host(`app.1440.news`) || Host(`url.1440.news`)"
- "traefik.http.routers.app-1440-news-redirect.entrypoints=http" - "traefik.http.routers.app-1440-news-redirect.entrypoints=http"
- "traefik.http.routers.app-1440-news-redirect.middlewares=https-redirect" - "traefik.http.routers.app-1440-news-redirect.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https" - "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.https-redirect.redirectscheme.permanent=true" - "traefik.http.middlewares.https-redirect.redirectscheme.permanent=true"
# Local development: HTTP only # Local development: HTTP only
- "traefik.http.routers.app-1440-news-local.rule=Host(`app.1440.localhost`)" - "traefik.http.routers.app-1440-news-local.rule=Host(`app.1440.localhost`) || Host(`url.1440.localhost`)"
- "traefik.http.routers.app-1440-news-local.entrypoints=http" - "traefik.http.routers.app-1440-news-local.entrypoints=http"
# Shared service # Shared service
- "traefik.http.services.app-1440-news.loadbalancer.server.port=4321" - "traefik.http.services.app-1440-news.loadbalancer.server.port=4321"
+2 -2
View File
@@ -162,13 +162,13 @@ func (c *Crawler) RecordClick(code string, r *http.Request) error {
} }
// GetShortURLForPost returns the short URL string for use in posts // GetShortURLForPost returns the short URL string for use in posts
// Format: https://app.1440.news/r/{code} // Format: https://url.1440.news/{code}
func (c *Crawler) GetShortURLForPost(originalURL string, itemID *int64, feedURL string) (string, error) { func (c *Crawler) GetShortURLForPost(originalURL string, itemID *int64, feedURL string) (string, error) {
shortURL, err := c.CreateShortURL(originalURL, itemID, feedURL) shortURL, err := c.CreateShortURL(originalURL, itemID, feedURL)
if err != nil { if err != nil {
return "", err return "", err
} }
return fmt.Sprintf("https://app.1440.news/r/%s", shortURL.Code), nil return fmt.Sprintf("https://url.1440.news/%s", shortURL.Code), nil
} }
// GetClickStats returns click statistics for a short URL // GetClickStats returns click statistics for a short URL