package atshorter import ( "context" "errors" "fmt" "log/slog" "net/http" "time" "github.com/bluesky-social/indigo/atproto/client" ) type HomeData struct { UsersShortURLs []ShortURL } type ShortURL struct { ID string URL string Did string OriginHost string } func (s *Server) HandleRedirect(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if id == "" { http.Redirect(w, r, "/", http.StatusSeeOther) return } shortURL, err := s.store.GetURLByID(id) if err != nil { if errors.Is(err, ErrorNotFound) { slog.Error("url with ID not found", "id", id) http.Error(w, "not found", http.StatusNotFound) return } slog.Error("getting URL by id", "id", id, "error", err) http.Error(w, "error fetching URL for redirect", http.StatusInternalServerError) return } http.Redirect(w, r, shortURL.URL, http.StatusSeeOther) return } func (s *Server) HandleHome(w http.ResponseWriter, r *http.Request) { tmpl := s.getTemplate("home.html") did, _ := s.currentSessionDID(r) if did == nil { http.Redirect(w, r, "/login", http.StatusFound) return } data := HomeData{} usersURLs, err := s.store.GetURLs(did.String()) if err != nil { slog.Error("fetching URLs", "error", err) tmpl.Execute(w, data) return } data.UsersShortURLs = usersURLs tmpl.Execute(w, data) } func (s *Server) HandleDeleteURL(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if id == "" { http.Redirect(w, r, "/", http.StatusSeeOther) return } did, sessionID := s.currentSessionDID(r) if did == nil { http.Redirect(w, r, "/login", http.StatusFound) return } shortURL, err := s.store.GetURLByID(id) if err != nil { slog.Error("looking up short URL", "error", err) http.Redirect(w, r, "/", http.StatusSeeOther) return } if shortURL.Did != did.String() { slog.Error("tried to delete record that doesn't belong to user") http.Error(w, "not authenticated", http.StatusUnauthorized) return } session, err := s.oauthClient.ResumeSession(r.Context(), *did, sessionID) if err != nil { http.Error(w, "not authenticated", http.StatusUnauthorized) return } api := session.APIClient() bodyReq := map[string]any{ "repo": shortURL.Did, "collection": "com.atshorter.shorturl", "rkey": id, } err = api.Post(r.Context(), "com.atproto.repo.deleteRecord", bodyReq, nil) if err != nil { slog.Error("failed to delete short URL record", "error", err) http.Redirect(w, r, "/", http.StatusFound) return } err = s.store.DeleteURL(id, did.String()) if err != nil { slog.Error("deleting URL from store", "error", err, "id", id, "did", did.String()) http.Redirect(w, r, "/", http.StatusSeeOther) return } http.Redirect(w, r, "/", http.StatusSeeOther) return } func (s *Server) HandleCreateShortURL(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { slog.Error("parsing form", "error", err) http.Error(w, "parsing form", http.StatusBadRequest) return } url := r.Form.Get("newURL") if url == "" { slog.Error("newURL not provided") http.Error(w, "missing newURL", http.StatusBadRequest) return } did, sessionID := s.currentSessionDID(r) if did == nil { http.Redirect(w, r, "/login", http.StatusFound) return } session, err := s.oauthClient.ResumeSession(r.Context(), *did, sessionID) if err != nil { http.Error(w, "not authenticated", http.StatusUnauthorized) return } rkey := TID() createdAt := time.Now() api := session.APIClient() record := ShortURLRecord{ URL: url, CreatedAt: createdAt, Origin: s.host, } bodyReq := map[string]any{ "repo": api.AccountDID.String(), "collection": "com.atshorter.shorturl", "rkey": rkey, "record": record, } err = api.Post(r.Context(), "com.atproto.repo.createRecord", bodyReq, nil) if err != nil { slog.Error("failed to create new short URL record", "error", err) http.Redirect(w, r, "/", http.StatusFound) return } err = s.store.CreateURL(rkey, url, did.String(), s.host, createdAt.UnixMilli()) if err != nil { slog.Error("store in local database", "error", err) } http.Redirect(w, r, "/", http.StatusFound) } type GetRecordResult struct { URI string `json:"uri"` CID string `json:"cid"` Value ShortURLRecord `json:"value"` } func (s *Server) getUrlRecord(ctx context.Context, didStr, rkey string) (ShortURLRecord, error) { host, err := s.lookupDidHost(ctx, didStr) if err != nil { return ShortURLRecord{}, fmt.Errorf("looking up did host: %w", err) } atClient := client.APIClient{ Client: s.httpClient, Host: host, } params := map[string]any{ "repo": didStr, "collection": "com.atshorter.shorturl", "rkey": rkey, } var res GetRecordResult err = atClient.Get(ctx, "com.atproto.repo.getRecord", params, &res) if err != nil { return ShortURLRecord{}, fmt.Errorf("calling getRecord: %w", err) } return res.Value, nil }