more thoughts

This commit is contained in:
David 2022-11-12 12:06:12 +07:00
parent dce1006228
commit 836a54599b
6 changed files with 133 additions and 35 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
config.json config.json
/friend

BIN
friend

Binary file not shown.

View File

@ -7,38 +7,99 @@ import (
"github.com/go-chi/chi/v5" "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 { func WebFingerHandler(wf *Webfinger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
requestedResource := r.URL.Query().Get("resource") requestedResource := r.URL.Query().Get("resource")
res, err := wf.FingerResource(requestedResource) res, err := wf.FingerResource(requestedResource)
if err != nil { if err != nil {
w.WriteHeader(http.StatusNotFound) RespondJSON(w, APIError{Error: "account not found"}, http.StatusNotFound)
w.Write([]byte(`{"error": "account not found"}`))
return return
} }
b, err := json.Marshal(res) RespondJSON(w, res, http.StatusOK)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(b)
} }
} }
func ProfileHandler(pp *ProfileProvider) http.HandlerFunc { func ProfileHandler(pp *ProfileProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "user") res, err := pp.Get()
res, err := pp.Get(userID)
if err != nil { if err != nil {
w.WriteHeader(http.StatusNotFound) RespondJSON(w, APIError{Error: "account not found"}, http.StatusNotFound)
w.Write([]byte(`{"error": "account not found"}`))
return return
} }
b, err := json.Marshal(res) RespondJSON(w, res, http.StatusOK)
if err != nil { }
w.WriteHeader(http.StatusInternalServerError) }
return
} // client retrieves messages for user
w.Write(b) 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)
} }
} }

16
main.go
View File

@ -8,18 +8,28 @@ import (
) )
func main() { 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 { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
pp, err := NewProfileProvider("hello.yetaga.in") pp, err := NewProfileProvider(username, domain)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
st := &InMemoryStreams{}
r := chi.NewRouter() r := chi.NewRouter()
r.Get("/.well-known/webfinger", WebFingerHandler(wf)) 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") log.Println("listening on http://0.0.0.0:8008")
err = http.ListenAndServe(":8008", r) err = http.ListenAndServe(":8008", r)

View File

@ -13,7 +13,7 @@ type Actor struct {
PublicKey PublicKey `json:"publicKey,omitempty"` PublicKey PublicKey `json:"publicKey,omitempty"`
Inbox string `json:"inbox,omitempty"` Inbox string `json:"inbox,omitempty"`
Outbox string `json:"outbo,omitempty"` Outbox string `json:"outbox,omitempty"`
Followers string `json:"followers,omitempty"` Followers string `json:"followers,omitempty"`
Following string `json:"following,omitempty"` Following string `json:"following,omitempty"`
@ -36,22 +36,24 @@ type PublicKey struct {
} }
type ProfileProvider struct { type ProfileProvider struct {
username string
domain string domain string
} }
func NewProfileProvider(domain string) (*ProfileProvider, error) { func NewProfileProvider(username, domain string) (*ProfileProvider, error) {
return &ProfileProvider{ return &ProfileProvider{
username: username,
domain: domain, domain: domain,
}, nil }, nil
} }
func (pp *ProfileProvider) Get(userID string) (*Actor, error) { func (pp *ProfileProvider) Get() (*Actor, error) {
userRoot := fmt.Sprintf("https://%s/users/%s", pp.domain, userID) userRoot := fmt.Sprintf("https://%s/users/%s", pp.domain, pp.username)
return &Actor{ return &Actor{
ID: userRoot, ID: userRoot,
Type: "Person", Type: "Person",
URL: fmt.Sprintf("https://%s/@%s", pp.domain, userID), URL: fmt.Sprintf("https://%s/@%s", pp.domain, pp.username),
Name: userID, Name: pp.username,
Inbox: userRoot + "/inbox", Inbox: userRoot + "/inbox",
Outbox: userRoot + "/outbox", Outbox: userRoot + "/outbox",
Followers: userRoot + "/followers", Followers: userRoot + "/followers",

View File

@ -1,6 +1,9 @@
package main package main
import "time" import (
"fmt"
"time"
)
type Stream struct { type Stream struct {
Context string `json:"@context"` Context string `json:"@context"`
@ -27,12 +30,33 @@ type Object struct {
Name string `json:"name"` 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")
}