80 lines
2.1 KiB
Go
80 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// generateShortCode creates a short code from a URL
|
|
// Uses first 6 characters of base64-encoded SHA256 hash
|
|
func generateShortCode(url string) string {
|
|
hash := sha256.Sum256([]byte(url))
|
|
encoded := base64.URLEncoding.EncodeToString(hash[:])
|
|
// Take first 6 chars, replace URL-unsafe chars
|
|
code := encoded[:6]
|
|
code = strings.ReplaceAll(code, "-", "x")
|
|
code = strings.ReplaceAll(code, "_", "y")
|
|
return code
|
|
}
|
|
|
|
// CreateShortURL creates or retrieves a short URL for the given original URL
|
|
func (c *Crawler) CreateShortURL(originalURL string, itemGUID, feedURL string) (string, error) {
|
|
// Check if we already have this URL
|
|
var existingCode string
|
|
err := c.db.QueryRow(`
|
|
SELECT code FROM short_urls WHERE original_url = $1
|
|
`, originalURL).Scan(&existingCode)
|
|
|
|
if err == nil {
|
|
return existingCode, nil
|
|
}
|
|
|
|
// Generate new short code
|
|
code := generateShortCode(originalURL)
|
|
|
|
// Handle collision by appending counter
|
|
baseCode := code
|
|
for i := 0; i < 100; i++ {
|
|
if i > 0 {
|
|
code = fmt.Sprintf("%s%d", baseCode, i)
|
|
}
|
|
|
|
var existingURL string
|
|
err := c.db.QueryRow("SELECT original_url FROM short_urls WHERE code = $1", code).Scan(&existingURL)
|
|
if err != nil {
|
|
// Code doesn't exist, use it
|
|
break
|
|
}
|
|
if existingURL == originalURL {
|
|
// Same URL, return existing
|
|
return code, nil
|
|
}
|
|
// Collision with different URL, try next
|
|
}
|
|
|
|
// Insert new short URL
|
|
_, err = c.db.Exec(`
|
|
INSERT INTO short_urls (code, original_url, item_guid, feed_url, created_at, click_count)
|
|
VALUES ($1, $2, $3, $4, $5, 0)
|
|
`, code, originalURL, NullableString(itemGUID), NullableString(feedURL), time.Now())
|
|
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create short URL: %v", err)
|
|
}
|
|
|
|
return code, nil
|
|
}
|
|
|
|
// GetShortURLForPost returns the short URL string for use in posts
|
|
// Format: https://news.1440.news/{code}
|
|
func (c *Crawler) GetShortURLForPost(originalURL, itemGUID, feedURL string) (string, error) {
|
|
code, err := c.CreateShortURL(originalURL, itemGUID, feedURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("https://news.1440.news/%s", code), nil
|
|
}
|