package http import ( "bytes" "fmt" "io" "net/http" "git.capotej.com/capotej/communique/controller" "github.com/gin-gonic/gin" "go.uber.org/zap" ) type Router struct { controller *controller.Controller log *zap.SugaredLogger } func NewRouter(controller *controller.Controller, log *zap.SugaredLogger) *Router { return &Router{controller: controller, log: log} } func render(c *gin.Context, resource map[string]interface{}, err error) { if resource == nil && err == nil { c.JSON(http.StatusNotFound, map[string]interface{}{}) } else if err != nil { jsonErr := c.Error(err) c.JSON(http.StatusInternalServerError, jsonErr.JSON()) } else { c.Writer.Header().Set("Content-Type", "application/activity+json") c.JSON(http.StatusOK, resource) } } func (s *Router) Start(zapWriter io.Writer) { router := gin.Default() router.SetTrustedProxies(nil) gin.DisableConsoleColor() gin.DefaultWriter = zapWriter // send gin logs to zap // Webfinger router.GET("/.well-known/webfinger", func(c *gin.Context) { resourceParam := c.Query("resource") resource, _ := s.controller.Webfinger(resourceParam) if resource != nil { c.Writer.Header().Set("Content-Type", "application/jrd+json") c.JSON(http.StatusOK, resource) } else { c.JSON(http.StatusNotFound, map[string]interface{}{}) } }) // Actor router.GET("/actors/:actor", func(c *gin.Context) { actorParam := c.Param("actor") resource, err := s.controller.Actor(actorParam) render(c, resource, err) }) //TODO use content addressing here so we can be cache friendly (like /actors/:actor/avatar/:avatar_sha256) // Actor avatar router.GET("/actors/:actor/avatar", func(c *gin.Context) { actorParam := c.Param("actor") avatarBytes, mediaType, err := s.controller.ActorAvatar(actorParam) if err != nil || avatarBytes == nil || mediaType == "" { c.Data(404, "text/plain", []byte("404 page not found")) } c.Header("Content-Length", fmt.Sprintf("%d", len(avatarBytes))) c.Data(200, mediaType, avatarBytes) }) // Actor Followers router.GET("/actors/:actor/followers", func(c *gin.Context) { actorParam := c.Param("actor") resource, err := s.controller.Followers(actorParam) render(c, resource, err) }) // Actor Following router.GET("/actors/:actor/following", func(c *gin.Context) { actorParam := c.Param("actor") resource, err := s.controller.Following(actorParam) render(c, resource, err) }) // Inbox router.POST("/actors/:actor/inbox", func(c *gin.Context) { log := s.log.With("type", "inbox") buf := new(bytes.Buffer) buf.ReadFrom(c.Request.Body) payload := buf.String() log.With( "signature", c.GetHeader("Signature"), ).With( "content-type", c.GetHeader("Content-Type"), ).With( "digest", c.GetHeader("digest"), ).With( "payload", payload, ).Debug("received inbox item") actorParam := c.Param("actor") err := s.controller.Inbox(actorParam, c.Request, buf.Bytes()) resource := map[string]interface{}{} render(c, resource, err) }) // Actor Outbox router.GET("/actors/:actor/outbox", func(c *gin.Context) { actorParam := c.Param("actor") resource, err := s.controller.OutboxCollection(actorParam) render(c, resource, err) }) // Actor Activity router.GET("/actors/:actor/activity/:id", func(c *gin.Context) { actorParam := c.Param("actor") idParam := c.Param("id") resource, err := s.controller.ActivityOrNote("activity", actorParam, idParam) render(c, resource, err) }) // Activity Note router.GET("/actors/:actor/activity/:id/note", func(c *gin.Context) { actorParam := c.Param("actor") idParam := c.Param("id") resource, err := s.controller.ActivityOrNote("note", actorParam, idParam) render(c, resource, err) }) router.Run() }