241 lines
7.2 KiB
Go
241 lines
7.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
pub "github.com/go-ap/activitypub"
|
|
"github.com/go-ap/errors"
|
|
json "github.com/go-ap/jsonld"
|
|
"github.com/go-ap/storage"
|
|
"net/http"
|
|
)
|
|
|
|
// CtxtKey type alias for the key under which we're storing the Collection Storage in the Request's context
|
|
type CtxtKey string
|
|
|
|
var RepositoryKey = CtxtKey("__repo")
|
|
|
|
// MethodValidator is the interface need to be implemented to specify if an HTTP request's method
|
|
// is supported by the implementor object
|
|
type MethodValidator interface {
|
|
ValidMethod(r *http.Request) bool
|
|
}
|
|
|
|
// RequestValidator is the interface need to be implemented to specify if the whole HTTP request
|
|
// is valid in the context of the implementor object
|
|
type RequestValidator interface {
|
|
ValidateRequest(r *http.Request) (int, error)
|
|
}
|
|
|
|
// ActivityHandlerFn is the type that we're using to represent handlers that process requests containing
|
|
// an ActivityStreams Activity. It needs to implement the http.Handler interface.
|
|
//
|
|
// It is considered that following the execution of the handler, we return a pair formed of a HTTP status together with
|
|
// an IRI representing a new Object - in the case of transitive activities that had a side effect, or
|
|
// an error.
|
|
// In the case of intransitive activities the iri will always be empty.
|
|
type ActivityHandlerFn func(CollectionType, *http.Request, storage.Repository) (pub.Item, int, error)
|
|
|
|
func (a ActivityHandlerFn) Storage(r *http.Request) (storage.Repository, error) {
|
|
ctxVal := r.Context().Value(RepositoryKey)
|
|
st, ok := ctxVal.(storage.Repository)
|
|
if !ok {
|
|
return nil, errors.Newf("Unable to find storage repository")
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
// ValidMethod validates if the current handler can process the current request
|
|
func (a ActivityHandlerFn) ValidMethod(r *http.Request) bool {
|
|
return r.Method == http.MethodPost
|
|
}
|
|
|
|
// ValidateRequest validates if the current handler can process the current request
|
|
func (a ActivityHandlerFn) ValidateRequest(r *http.Request) (int, error) {
|
|
if !a.ValidMethod(r) {
|
|
return http.StatusNotAcceptable, errors.MethodNotAllowedf("Invalid HTTP method %s", r.Method)
|
|
}
|
|
return http.StatusOK, nil
|
|
}
|
|
|
|
// ServeHTTP implements the http.Handler interface for the ActivityHandlerFn type
|
|
func (a ActivityHandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
var dat []byte
|
|
var it pub.Item
|
|
var err error
|
|
var status = http.StatusInternalServerError
|
|
|
|
if status, err = a.ValidateRequest(r); err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
// TODO(marius): we need a better mechanism than loading it from the Request Context
|
|
st, err := a.Storage(r)
|
|
if err != nil {
|
|
dat = []byte(err.Error())
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
if it, status, err = a(Typer.Type(r), r, st); err != nil {
|
|
dat = []byte(err.Error())
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
contentType := json.ContentType
|
|
if act, err := pub.ToActivity(it); err == nil {
|
|
if act.Object.IsLink() {
|
|
col, cnt, err := st.LoadObjects(act.Object)
|
|
if err == nil && cnt == 1 {
|
|
act.Object = col[0]
|
|
}
|
|
}
|
|
if dat, err = pub.MarshalJSON(act.Object); err != nil {
|
|
dat = dat[:]
|
|
contentType = "application/json"
|
|
}
|
|
}
|
|
|
|
switch status {
|
|
case http.StatusCreated:
|
|
if len(dat) == 0 {
|
|
dat = []byte("CREATED")
|
|
}
|
|
w.Header().Set("Location", it.GetLink().String())
|
|
case http.StatusGone:
|
|
if len(dat) == 0 {
|
|
dat = []byte("DELETED")
|
|
}
|
|
default:
|
|
contentType = json.ContentType
|
|
dat, _ = pub.MarshalJSON(it)
|
|
}
|
|
w.Header().Set("Content-Type", contentType)
|
|
w.WriteHeader(status)
|
|
w.Write(dat)
|
|
}
|
|
|
|
// CollectionHandlerFn is the type that we're using to represent handlers that will return ActivityStreams
|
|
// Collection or OrderedCollection objects. It needs to implement the http.Handler interface.
|
|
type CollectionHandlerFn func(CollectionType, *http.Request, storage.CollectionLoader) (pub.CollectionInterface, error)
|
|
|
|
func (c CollectionHandlerFn) Storage(r *http.Request) (storage.CollectionLoader, error) {
|
|
ctxVal := r.Context().Value(RepositoryKey)
|
|
repo, ok := ctxVal.(storage.CollectionLoader)
|
|
if !ok {
|
|
return nil, errors.Newf("Unable to find Collection storage")
|
|
}
|
|
return repo, nil
|
|
}
|
|
|
|
// ValidMethod validates if the current handler can process the current request
|
|
func (c CollectionHandlerFn) ValidMethod(r *http.Request) bool {
|
|
return r.Method == http.MethodGet || r.Method == http.MethodHead
|
|
}
|
|
|
|
// ValidateRequest validates if the current handler can process the current request
|
|
func (c CollectionHandlerFn) ValidateRequest(r *http.Request) (int, error) {
|
|
if !c.ValidMethod(r) {
|
|
return http.StatusMethodNotAllowed, errors.MethodNotAllowedf("Invalid HTTP method %s", r.Method)
|
|
}
|
|
return http.StatusOK, nil
|
|
}
|
|
|
|
// ServeHTTP implements the http.Handler interface for the CollectionHandlerFn type
|
|
func (c CollectionHandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
var dat []byte
|
|
|
|
var status = http.StatusInternalServerError
|
|
var err error
|
|
|
|
status, err = c.ValidateRequest(r)
|
|
if err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
st, err := c.Storage(r)
|
|
if err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
col, err := c(Typer.Type(r), r, st)
|
|
if err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if dat, err = json.WithContext(json.IRI(pub.ActivityBaseURI)).Marshal(col); err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
status = http.StatusOK
|
|
w.Header().Set("Content-Type", "application/activity+json")
|
|
w.WriteHeader(status)
|
|
if r.Method == http.MethodGet {
|
|
w.Write(dat)
|
|
}
|
|
}
|
|
|
|
// ItemHandlerFn is the type that we're using to represent handlers that return ActivityStreams
|
|
// objects. It needs to implement the http.Handler interface
|
|
type ItemHandlerFn func(*http.Request, storage.ObjectLoader) (pub.Item, error)
|
|
|
|
func (i ItemHandlerFn) Storage(r *http.Request) (storage.ObjectLoader, error) {
|
|
ctxVal := r.Context().Value(RepositoryKey)
|
|
st, ok := ctxVal.(storage.ObjectLoader)
|
|
if !ok {
|
|
return nil, errors.Newf("Unable to find Object storage")
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
// ValidMethod validates if the current handler can process the current request
|
|
func (i ItemHandlerFn) ValidMethod(r *http.Request) bool {
|
|
return r.Method == http.MethodGet || r.Method == http.MethodHead
|
|
}
|
|
|
|
// ValidateRequest validates if the current handler can process the current request
|
|
func (i ItemHandlerFn) ValidateRequest(r *http.Request) (int, error) {
|
|
if !i.ValidMethod(r) {
|
|
return http.StatusMethodNotAllowed, errors.MethodNotAllowedf("Invalid HTTP method %s", r.Method)
|
|
}
|
|
return http.StatusOK, nil
|
|
}
|
|
|
|
// ServeHTTP implements the http.Handler interface for the ItemHandlerFn type
|
|
func (i ItemHandlerFn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
var dat []byte
|
|
var err error
|
|
status := http.StatusInternalServerError
|
|
|
|
status, err = i.ValidateRequest(r)
|
|
if err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
st, err := i.Storage(r)
|
|
if err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
it, err := i(r, st)
|
|
if err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
if dat, err = json.WithContext(json.IRI(pub.ActivityBaseURI)).Marshal(it); err != nil {
|
|
errors.HandleError(err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
status = http.StatusOK
|
|
w.Header().Set("Content-Type", "application/activity+json")
|
|
w.WriteHeader(status)
|
|
if r.Method == http.MethodGet {
|
|
w.Write(dat)
|
|
}
|
|
}
|