diff --git a/.gitignore b/.gitignore index d344ba6..54e8e60 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ config.json +/friend diff --git a/friend b/friend deleted file mode 100755 index aaf8f43..0000000 Binary files a/friend and /dev/null differ diff --git a/handlers.go b/handlers.go index cad9e47..cd35588 100644 --- a/handlers.go +++ b/handlers.go @@ -7,38 +7,99 @@ import ( "github.com/go-chi/chi/v5" ) +type APIError struct { + Error string `json:"error"` +} + +// since we only have the single username to worry about, we just wrap everything up here +func UserExistsMiddleware(user string, h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + userID := chi.URLParam(r, "user") + if userID != user { + RespondJSON(w, APIError{Error: "account not found"}, http.StatusNotFound) + return + } + h.ServeHTTP(w, r) + } +} + +func RespondJSON(w http.ResponseWriter, content any, statuscode int) { + b, err := json.Marshal(content) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(statuscode) + w.Write(b) +} + func WebFingerHandler(wf *Webfinger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { requestedResource := r.URL.Query().Get("resource") res, err := wf.FingerResource(requestedResource) if err != nil { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte(`{"error": "account not found"}`)) + RespondJSON(w, APIError{Error: "account not found"}, http.StatusNotFound) return } - b, err := json.Marshal(res) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - w.Write(b) + RespondJSON(w, res, http.StatusOK) } } func ProfileHandler(pp *ProfileProvider) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - userID := chi.URLParam(r, "user") - res, err := pp.Get(userID) + res, err := pp.Get() if err != nil { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte(`{"error": "account not found"}`)) + RespondJSON(w, APIError{Error: "account not found"}, http.StatusNotFound) return } - b, err := json.Marshal(res) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - w.Write(b) + RespondJSON(w, res, http.StatusOK) + } +} + +// client retrieves messages for user +func GetInboxHandler(streams *InMemoryStreams) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + stream, err := streams.GetActivitiesToUser() + if err != nil { + RespondJSON(w, APIError{Error: err.Error()}, http.StatusInternalServerError) + return + } + RespondJSON(w, stream, http.StatusOK) + } +} + +// handle incoming activities for user +func PostInboxHandler(streams *InMemoryStreams) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + err := streams.AddActivityToUser() + if err != nil { + RespondJSON(w, APIError{Error: err.Error()}, http.StatusInternalServerError) + return + } + RespondJSON(w, nil, http.StatusOK) + } +} + +// other servers/clients read a user's activities +func GetOutboxHandler(streams *InMemoryStreams) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + stream, err := streams.GetActivitiesFromUser() + if err != nil { + RespondJSON(w, APIError{Error: err.Error()}, http.StatusInternalServerError) + return + } + RespondJSON(w, stream, http.StatusOK) + } +} + +// client adds new activity for user +func PostOutboxHandler(streams *InMemoryStreams) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + err := streams.AddActivityFromUser() + if err != nil { + RespondJSON(w, APIError{Error: err.Error()}, http.StatusInternalServerError) + return + } + RespondJSON(w, nil, http.StatusOK) } } diff --git a/main.go b/main.go index 822d832..7ac583f 100644 --- a/main.go +++ b/main.go @@ -8,18 +8,28 @@ import ( ) func main() { - wf, err := NewWebfinger("dma", "hello.yetaga.in") + // this will be configurable in the future obviously + username := "dma" + domain := "hello.yetaga.in" + + wf, err := NewWebfinger(username, domain) if err != nil { log.Fatalln(err) } - pp, err := NewProfileProvider("hello.yetaga.in") + pp, err := NewProfileProvider(username, domain) if err != nil { log.Fatalln(err) } + st := &InMemoryStreams{} r := chi.NewRouter() r.Get("/.well-known/webfinger", WebFingerHandler(wf)) - r.Get("/users/{user}", ProfileHandler(pp)) + r.Get("/users/{user}", UserExistsMiddleware(username, ProfileHandler(pp))) + r.Get("/users/{user}/inbox", UserExistsMiddleware(username, GetInboxHandler(st))) + r.Post("/users/{user}/inbox", UserExistsMiddleware(username, PostInboxHandler(st))) + r.Get("/users/{user}/outbox", UserExistsMiddleware(username, GetOutboxHandler(st))) + r.Post("/users/{user}/outbox", UserExistsMiddleware(username, PostOutboxHandler(st))) + r.Post("/inbox", PostInboxHandler(st)) // "shared" inbox log.Println("listening on http://0.0.0.0:8008") err = http.ListenAndServe(":8008", r) diff --git a/profile.go b/profile.go index ef3d013..99fb75d 100644 --- a/profile.go +++ b/profile.go @@ -13,7 +13,7 @@ type Actor struct { PublicKey PublicKey `json:"publicKey,omitempty"` Inbox string `json:"inbox,omitempty"` - Outbox string `json:"outbo,omitempty"` + Outbox string `json:"outbox,omitempty"` Followers string `json:"followers,omitempty"` Following string `json:"following,omitempty"` @@ -36,22 +36,24 @@ type PublicKey struct { } type ProfileProvider struct { - domain string + username string + domain string } -func NewProfileProvider(domain string) (*ProfileProvider, error) { +func NewProfileProvider(username, domain string) (*ProfileProvider, error) { return &ProfileProvider{ - domain: domain, + username: username, + domain: domain, }, nil } -func (pp *ProfileProvider) Get(userID string) (*Actor, error) { - userRoot := fmt.Sprintf("https://%s/users/%s", pp.domain, userID) +func (pp *ProfileProvider) Get() (*Actor, error) { + userRoot := fmt.Sprintf("https://%s/users/%s", pp.domain, pp.username) return &Actor{ ID: userRoot, Type: "Person", - URL: fmt.Sprintf("https://%s/@%s", pp.domain, userID), - Name: userID, + URL: fmt.Sprintf("https://%s/@%s", pp.domain, pp.username), + Name: pp.username, Inbox: userRoot + "/inbox", Outbox: userRoot + "/outbox", Followers: userRoot + "/followers", diff --git a/streams.go b/streams.go index 582bea7..8528c99 100644 --- a/streams.go +++ b/streams.go @@ -1,6 +1,9 @@ package main -import "time" +import ( + "fmt" + "time" +) type Stream struct { Context string `json:"@context"` @@ -27,12 +30,33 @@ type Object struct { Name string `json:"name"` } -type Streams struct{} +type InMemoryStreams struct { +} -func (s *Streams) Inbox() +// view activities from other people directed to out user +func (s *InMemoryStreams) GetActivitiesToUser() (*Stream, error) { + return nil, fmt.Errorf("not implemented") +} -func (s *Streams) Outbox() +// view activities created by our user +func (s *InMemoryStreams) GetActivitiesFromUser() (*Stream, error) { + return nil, fmt.Errorf("not implemented") +} -func (s *Streams) Followers() +// insert activities from other people directed to our user +func (s *InMemoryStreams) AddActivityToUser() error { + return fmt.Errorf("not implemented") +} -func (s *Streams) Followed() +// insert activities from our user to be dispatched +func (s *InMemoryStreams) AddActivityFromUser() error { + return fmt.Errorf("not implemented") +} + +func (s *InMemoryStreams) Followers() (*Stream, error) { + return nil, fmt.Errorf("not implemented") +} + +func (s *InMemoryStreams) Followed() (*Stream, error) { + return nil, fmt.Errorf("not implemented") +}