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) } }