Files
Snip/backend/internal/httpx/api/links.go
T
2026-06-15 21:25:57 +07:00

149 lines
3.5 KiB
Go

package api
import (
"net/http"
"strconv"
"strings"
"github.com/snip/backend/internal/domain"
"github.com/snip/backend/internal/service"
)
// /api/v1/links — POST create (auth optional), GET list (auth required)
func (a *API) linksCollection(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
a.createLink(w, r)
case http.MethodGet:
a.listLinks(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (a *API) createLink(w http.ResponseWriter, r *http.Request) {
var req createReq
if err := decode(r, &req); err != nil {
writeError(w, domain.ErrInvalidURL)
return
}
mode := domain.Mode(req.Mode)
if mode != domain.ModeRandom && mode != domain.ModeMemorable && mode != domain.ModeCustom {
mode = domain.ModeRandom
}
link, err := a.links.Create(r.Context(), service.CreateInput{
LongURL: req.LongURL,
Mode: mode,
CustomAlias: req.CustomAlias,
Pin: req.Pin,
}, a.userID(r))
if err != nil {
writeError(w, err)
return
}
writeJSON(w, http.StatusCreated, toDTO(link))
}
func (a *API) listLinks(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
page, _ := strconv.Atoi(q.Get("page"))
pageSize, _ := strconv.Atoi(q.Get("pageSize"))
result, err := a.links.List(r.Context(), a.userID(r), q.Get("q"), page, pageSize)
if err != nil {
writeError(w, err)
return
}
items := make([]linkDTO, 0, len(result.Items))
for i := range result.Items {
items = append(items, toDTO(&result.Items[i]))
}
writeJSON(w, http.StatusOK, listResponse{
Items: items,
Total: result.Total,
Page: result.Page,
PageSize: result.PageSize,
})
}
func (a *API) linkStats(w http.ResponseWriter, r *http.Request) {
stats, err := a.links.Stats(r.Context(), a.userID(r))
if err != nil {
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, stats)
}
// /api/v1/links/{id} — PATCH, DELETE
// /api/v1/links/{id}/pin — PUT, DELETE
func (a *API) linkItem(w http.ResponseWriter, r *http.Request) {
rest := pathTail(r.URL.Path, "/api/v1/links/")
parts := strings.Split(strings.Trim(rest, "/"), "/")
id := parts[0]
if id == "" {
w.WriteHeader(http.StatusNotFound)
return
}
owner := a.userID(r)
// /{id}/pin
if len(parts) == 2 && parts[1] == "pin" {
a.managePin(w, r, id, owner)
return
}
if len(parts) != 1 {
w.WriteHeader(http.StatusNotFound)
return
}
switch r.Method {
case http.MethodPatch:
var req updateReq
if err := decode(r, &req); err != nil {
writeError(w, domain.ErrInvalidURL)
return
}
link, err := a.links.UpdateDestination(r.Context(), id, owner, req.LongURL)
if err != nil {
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, toDTO(link))
case http.MethodDelete:
if err := a.links.Delete(r.Context(), id, owner); err != nil {
writeError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (a *API) managePin(w http.ResponseWriter, r *http.Request, id, owner string) {
switch r.Method {
case http.MethodPut:
var req pinReq
if err := decode(r, &req); err != nil {
writeError(w, domain.ErrInvalidPin)
return
}
link, err := a.links.SetPin(r.Context(), id, owner, req.Pin)
if err != nil {
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, toDTO(link))
case http.MethodDelete:
link, err := a.links.SetPin(r.Context(), id, owner, "")
if err != nil {
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, toDTO(link))
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}