You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

109 lines
3.1 KiB

package main
import (
"encoding/csv"
"encoding/json"
"log"
"net/http"
"strconv"
"strings"
)
// HandleListLinks handles GET /api/admin/links — returns all records.
func (h *Handler) HandleListLinks(w http.ResponseWriter, r *http.Request) {
records, err := h.store.ListAll()
if err != nil {
log.Printf("list links error: %v", err)
jsonError(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(records)
}
// HandleUpdateLink handles PUT /api/admin/links/{code} — updates a link's URL.
func (h *Handler) HandleUpdateLink(w http.ResponseWriter, r *http.Request) {
code := r.PathValue("code")
if code == "" {
jsonError(w, "missing short code", http.StatusBadRequest)
return
}
var req struct {
URL string `json:"url"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
jsonError(w, "invalid request body", http.StatusBadRequest)
return
}
req.URL = strings.TrimSpace(req.URL)
if req.URL == "" {
jsonError(w, "url is required", http.StatusBadRequest)
return
}
if !strings.HasPrefix(req.URL, "http://") && !strings.HasPrefix(req.URL, "https://") {
jsonError(w, "url must start with http:// or https://", http.StatusBadRequest)
return
}
if err := h.store.Update(code, req.URL); err != nil {
log.Printf("update link error: %v", err)
jsonError(w, "short link not found", http.StatusNotFound)
return
}
rec, _ := h.store.FindByCode(code)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(rec)
}
// HandleDeleteLink handles DELETE /api/admin/links/{code} — deletes a link.
func (h *Handler) HandleDeleteLink(w http.ResponseWriter, r *http.Request) {
code := r.PathValue("code")
if code == "" {
jsonError(w, "missing short code", http.StatusBadRequest)
return
}
if err := h.store.Delete(code); err != nil {
log.Printf("delete link error: %v", err)
jsonError(w, "short link not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
}
// HandleExport handles GET /api/admin/export?format=json|csv — exports all data.
func (h *Handler) HandleExport(w http.ResponseWriter, r *http.Request) {
records, err := h.store.ListAll()
if err != nil {
log.Printf("export error: %v", err)
jsonError(w, "internal error", http.StatusInternalServerError)
return
}
format := r.URL.Query().Get("format")
if format == "csv" {
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
w.Header().Set("Content-Disposition", "attachment; filename=\"shortlinks.csv\"")
wr := csv.NewWriter(w)
wr.Write([]string{"code", "original_url", "visit_count", "created_at"})
for _, rec := range records {
wr.Write([]string{
rec.Code,
rec.OriginalURL,
strconv.FormatInt(rec.VisitCount, 10),
rec.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"),
})
}
wr.Flush()
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Disposition", "attachment; filename=\"shortlinks.json\"")
json.NewEncoder(w).Encode(records)
}