diff --git a/greataped/app/activitypub/followers.go b/greataped/app/activitypub/followers.go new file mode 100644 index 0000000..9a41471 --- /dev/null +++ b/greataped/app/activitypub/followers.go @@ -0,0 +1,21 @@ +package activitypub + +import "encoding/json" + +type Followers struct { + Context string `json:"@context"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + TotalItems int `json:"totalItems"` + OrderedItems interface{} `json:"orderedItems,omitempty"` +} + +func UnmarshalFollowers(data []byte) (Followers, error) { + var o Followers + err := json.Unmarshal(data, &o) + return o, err +} + +func (o *Followers) Marshal() ([]byte, error) { + return json.Marshal(o) +} diff --git a/greataped/app/models/repos/followers.go b/greataped/app/models/repos/followers.go index d23a5b7..c5829ab 100644 --- a/greataped/app/models/repos/followers.go +++ b/greataped/app/models/repos/followers.go @@ -9,6 +9,7 @@ import ( // Follower struct defines a follower type Follower struct { gorm.Model + Target string `gorm:"not null"` Handle string `gorm:"not null"` } @@ -16,3 +17,8 @@ type Follower struct { func CreateFollower(follower *Follower) *gorm.DB { return db.DB.Create(follower) } + +// FindFollowers finds the user's followers +func FindFollowers(dest interface{}, userIden interface{}) *gorm.DB { + return db.DB.Model(&Follower{}).Find(dest, "`target` = ?", userIden) +} diff --git a/greataped/app/models/repos/following.go b/greataped/app/models/repos/following.go index 4543d62..4025bf6 100644 --- a/greataped/app/models/repos/following.go +++ b/greataped/app/models/repos/following.go @@ -6,13 +6,19 @@ import ( "gorm.io/gorm" ) -// Following struct defines a follower +// Following struct defines an account that the user follows type Following struct { gorm.Model + Target string `gorm:"not null"` Handle string `gorm:"not null"` } -// CreateFollowing creates a new entry in the followers's table +// CreateFollowing creates a new entry in the following's table func CreateFollowing(following *Following) *gorm.DB { return db.DB.Create(following) } + +// FindFollowing finds what accounts the user is following +func FindFollowing(dest interface{}, userIden interface{}) *gorm.DB { + return db.DB.Model(&Follower{}).Find(dest, "`handle` = ?", userIden) +} diff --git a/greataped/app/models/types/follower.go b/greataped/app/models/types/follower.go index d7851dc..d7d95ae 100644 --- a/greataped/app/models/types/follower.go +++ b/greataped/app/models/types/follower.go @@ -1,9 +1,11 @@ package types type FollowerDTO struct { + Target string `json:"target" validate:"required"` Handle string `json:"handle" validate:"required"` } type FollowerResponse struct { - Handler string `json:"handler"` + Target string `json:"target"` + Handle string `json:"handle"` } diff --git a/greataped/app/models/types/following.go b/greataped/app/models/types/following.go new file mode 100644 index 0000000..2bb9917 --- /dev/null +++ b/greataped/app/models/types/following.go @@ -0,0 +1,11 @@ +package types + +type FollowingDTO struct { + Target string `json:"target" validate:"required"` + Handle string `json:"handle" validate:"required"` +} + +type FollowingResponse struct { + Target string `json:"target"` + Handle string `json:"handle"` +} diff --git a/greataped/app/routes/followers.go b/greataped/app/routes/followers.go index f7a3c59..02c66d8 100644 --- a/greataped/app/routes/followers.go +++ b/greataped/app/routes/followers.go @@ -1,10 +1,39 @@ package routes import ( + "app/activitypub" + "app/models/repos" + "app/models/types" + "config" . "contracts" "server/route" ) -var Followers = route.New(HttpGet, "/u/:name/followers", func(x IContext) error { - return x.JSON(struct{}{}) +var Followers = route.New(HttpGet, "/u/:username/followers", func(x IContext) error { + username := x.Request().Params("username") + actor := x.StringUtil().Format("%s://%s/u/%s", config.PROTOCOL, config.DOMAIN, username) + id := x.StringUtil().Format("%s://%s/u/%s/followers", config.PROTOCOL, config.DOMAIN, username) + + followers := &[]types.FollowerResponse{} + err := repos.FindFollowers(followers, actor).Error + if err != nil { + x.InternalServerError(err.Error()) + } + + items := []string{} + for _, follower := range *followers { + items = append(items, follower.Handle) + } + + result := &activitypub.Followers{ + Context: "https://www.w3.org/ns/activitystreams", + ID: id, + Type: "OrderedCollection", + TotalItems: len(items), + OrderedItems: items, + } + + json, _ := result.Marshal() + x.Response().Header("Content-Type", "application/activity+json; charset=utf-8") + return x.WriteString(string(json)) }) diff --git a/greataped/app/routes/following.go b/greataped/app/routes/following.go new file mode 100644 index 0000000..d472c78 --- /dev/null +++ b/greataped/app/routes/following.go @@ -0,0 +1,39 @@ +package routes + +import ( + "app/activitypub" + "app/models/repos" + "app/models/types" + "config" + . "contracts" + "server/route" +) + +var Following = route.New(HttpGet, "/u/:username/following", func(x IContext) error { + username := x.Request().Params("username") + actor := x.StringUtil().Format("%s://%s/u/%s", config.PROTOCOL, config.DOMAIN, username) + id := x.StringUtil().Format("%s://%s/u/%s/following", config.PROTOCOL, config.DOMAIN, username) + + followings := &[]types.FollowerResponse{} + err := repos.FindFollowing(followings, actor).Error + if err != nil { + x.InternalServerError(err.Error()) + } + + items := []string{} + for _, following := range *followings { + items = append(items, following.Target) + } + + result := &activitypub.Followers{ + Context: "https://www.w3.org/ns/activitystreams", + ID: id, + Type: "OrderedCollection", + TotalItems: len(items), + OrderedItems: items, + } + + json, _ := result.Marshal() + x.Response().Header("Content-Type", "application/activity+json; charset=utf-8") + return x.WriteString(string(json)) +}) diff --git a/greataped/app/routes/inbox.go b/greataped/app/routes/inbox.go index 4105e75..234a05e 100644 --- a/greataped/app/routes/inbox.go +++ b/greataped/app/routes/inbox.go @@ -40,6 +40,7 @@ var InboxPost = route.New(HttpPost, "/u/:username/inbox", func(x IContext) error } url := activity.Actor + follower := activity.Actor var inbox string { @@ -60,13 +61,16 @@ var InboxPost = route.New(HttpPost, "/u/:username/inbox", func(x IContext) error Object: activity, }) - output := &struct { - content string - }{} - - if err := x.PostActivityStream(inbox, keyId, key.PrivateKey, data, output); err != nil { + if err := x.PostActivityStream(inbox, keyId, key.PrivateKey, data, nil); err != nil { return x.InternalServerError(err.Error()) } + + if err := repos.CreateFollower(&repos.Follower{ + Target: x.StringUtil().Format("%s://%s/u/%s", config.PROTOCOL, config.DOMAIN, username), + Handle: follower, + }); err.Error != nil { + return x.Conflict(err.Error.Error()) + } } return x.Nothing() diff --git a/greataped/app/views/user.html b/greataped/app/views/user.html index f7eb224..5353603 100644 --- a/greataped/app/views/user.html +++ b/greataped/app/views/user.html @@ -22,7 +22,9 @@
-

{{ .Title }}

+
+

{{ .Title }}

+

- Following +