From 199f77e70c5dcbe63d410bfb0c268b0e636a66c4 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Mon, 14 Aug 2023 17:52:49 +0800 Subject: [PATCH] add followship feature's backend logic implement --- .vscode/launch.json | 3 + CHANGELOG.md | 24 +++- ROADMAP.md | 10 +- ...0409-关于Followship功能项的设计.md | 10 +- internal/conf/db.go | 1 + internal/conf/setting.go | 1 + internal/core/core.go | 1 + internal/core/ms/user.go | 5 +- internal/core/user.go | 10 ++ internal/dao/jinzhu/contacts.go | 2 +- internal/dao/jinzhu/dbr/following.go | 87 +++++++++++++++ internal/dao/jinzhu/following.go | 104 ++++++++++++++++++ internal/dao/jinzhu/gorm.go | 2 + internal/dao/jinzhu/jinzhu.go | 2 + internal/model/web/core.go | 4 +- internal/model/web/loose.go | 4 +- internal/model/web/xerror.go | 5 + internal/servants/web/core.go | 8 +- internal/servants/web/followship.go | 40 +++++-- internal/servants/web/loose.go | 29 +++-- .../mysql/0010_user_following.down.sql | 1 + .../mysql/0010_user_following.up.sql | 11 ++ .../postgres/0009_user_following.down.sql | 1 + .../postgres/0009_user_following.up.sql | 10 ++ .../sqlite3/0010_user_following.down.sql | 1 + .../sqlite3/0010_user_following.up.sql | 15 +++ scripts/paopao-mysql.sql | 16 +++ scripts/paopao-postgres.sql | 12 ++ scripts/paopao-sqlite3.sql | 24 ++++ 29 files changed, 406 insertions(+), 37 deletions(-) create mode 100644 internal/dao/jinzhu/dbr/following.go create mode 100644 internal/dao/jinzhu/following.go create mode 100644 scripts/migration/mysql/0010_user_following.down.sql create mode 100644 scripts/migration/mysql/0010_user_following.up.sql create mode 100644 scripts/migration/postgres/0009_user_following.down.sql create mode 100644 scripts/migration/postgres/0009_user_following.up.sql create mode 100644 scripts/migration/sqlite3/0010_user_following.down.sql create mode 100644 scripts/migration/sqlite3/0010_user_following.up.sql diff --git a/.vscode/launch.json b/.vscode/launch.json index fb60435b..0c5052b4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,9 @@ "request": "launch", "mode": "exec", "program": "${workspaceFolder}/.vscode/__debug_bin", + "args":[ + "serve" + ], "preLaunchTask": "go: build (debug)", "cwd": "${workspaceFolder}" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e0dd10..dd90ef83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,28 @@ All notable changes to paopao-ce are documented in this file. - frontend: re-add stars page embed to profile page. [#339](https://github.com/rocboss/paopao-ce/pull/339) - simple support for user posts filter by style(post/comment/media/star). [#345](https://github.com/rocboss/paopao-ce/pull/345) mirgration database first(sql ddl file in `scripts/migration/**/*_create_view_post_filter.up.sql`): - ```sql - CREATE VIEW p_post_by_media AS SELECT post.*FROM (SELECT DISTINCT post_id FROM p_post_content WHERE (TYPE=3 OR TYPE=4 OR TYPE=7 OR TYPE=8) AND is_del=0) media JOIN p_post post ON media.post_id=post.ID WHERE post.is_del=0; - CREATE VIEW p_post_by_comment AS SELECT P.*,C.user_id comment_user_id FROM (SELECT post_id,user_id FROM p_comment WHERE is_del=0 UNION SELECT post_id,reply.user_id user_id FROM p_comment_reply reply JOIN p_comment COMMENT ON reply.comment_id=COMMENT.ID WHERE reply.is_del=0 AND COMMENT.is_del=0) C JOIN p_post P ON C.post_id=P.ID WHERE P.is_del=0; - ``` + ```sql + CREATE VIEW p_post_by_media AS SELECT post.*FROM (SELECT DISTINCT post_id FROM p_post_content WHERE (TYPE=3 OR TYPE=4 OR TYPE=7 OR TYPE=8) AND is_del=0) media JOIN p_post post ON media.post_id=post.ID WHERE post.is_del=0; + CREATE VIEW p_post_by_comment AS SELECT P.*,C.user_id comment_user_id FROM (SELECT post_id,user_id FROM p_comment WHERE is_del=0 UNION SELECT post_id,reply.user_id user_id FROM p_comment_reply reply JOIN p_comment COMMENT ON reply.comment_id=COMMENT.ID WHERE reply.is_del=0 AND COMMENT.is_del=0) C JOIN p_post P ON C.post_id=P.ID WHERE P.is_del=0; + ``` - add user highlight tweet support include custom tweet set to highlight and list in user/profile page. - add cli subcommand to start paopao-ce serve or other task. [#354](https://github.com/rocboss/paopao-ce/pull/354) +- add `Friendship` feature . [#355](https://github.com/rocboss/paopao-ce/pull/354) + mirgration database first(sql ddl file in `scripts/migration/**/*_create_view_post_filter.up.sql`): + ```sql + DROP TABLE IF EXISTS p_following; + CREATE TABLE p_following (ID BIGSERIAL PRIMARY KEY,user_id BIGINT NOT NULL,follow_id BIGINT NOT NULL,is_del SMALLINT NOT NULL DEFAULT 0,created_on BIGINT NOT NULL DEFAULT 0,modified_on BIGINT NOT NULL DEFAULT 0,deleted_on BIGINT NOT NULL DEFAULT 0); + CREATE INDEX idx_following_user_follow ON p_following USING btree (user_id,follow_id); + ``` + custom set config.yaml in `Features` section add `Followship` to enable Followship feature: + ```yaml + ... + # add Followship to enable this feature + Features: + Default: ["Meili", "LoggerMeili", "Base", "Sqlite3", "BigCacheIndex", "MinIO", "Followship"] + Base: ["Redis", "PhoneBind"] + ... + ``` ### Changed - change man content width to 600px and optimize tweet/comment/replay text length. [#333](https://github.com/rocboss/paopao-ce/pull/333) diff --git a/ROADMAP.md b/ROADMAP.md index fa77d3be..e828c2ec 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,18 +3,22 @@ ## paopao-ce roadmap #### dev+ -* [ ] add `Followship` feature * [ ] add `Auth:Bcrypt` feature * [ ] add `Auth:MD5` feature (just for compatible) -* [x] add extend base ORM code for implement data logic base sqlx/sqlc * [ ] optimize media tweet submit logic * [ ] optimize search logic service +#### v0.4.0 +* [x] add `Followship` feature. +* [x] add extend base ORM code for implement data logic base sqlx/sqlc. +* [x] user/profile page add comment/highlight/media/likes sub-page. +* [x] add tweet highlight feature to enable user set a tweet as highlight. + #### v0.3.0 * [x] remove `Deprecated:OldWeb` feature * [x] add user topic follow feature support * [x] add tweet link share support -* [ ] add comment thumbsUp/thumbsDown support +* [x] add comment thumbsUp/thumbsDown support * [x] add `RedisCacheIndex` feature * [x] add `Sentry` feature diff --git a/docs/proposal/22110409-关于Followship功能项的设计.md b/docs/proposal/22110409-关于Followship功能项的设计.md index 4fb4ef4e..9a385d71 100644 --- a/docs/proposal/22110409-关于Followship功能项的设计.md +++ b/docs/proposal/22110409-关于Followship功能项的设计.md @@ -1,6 +1,6 @@ | 编号 | 作者 | 发表时间 | 变更时间 | 版本 | 状态 | | ----- | ----- | ----- | ----- | ----- | ----- | -| 22110409| 北野 | 2022-11-04 | 2022-11-21 | v0.1 | 提议 | +| 22110409| 北野 | 2022-11-04 | 2023-08-14 | v1.0 | 提议 | ### 关于Followship功能项的设计 Followship是实现类似Twitter Timeline模式**关注者模型**的时间线信息流,广场推文列表的生成将主要与推文时间、用户的关注者相关。Twitter的推文消息流是非常智能的,用户体验也非常好,这得益于其背后的智能推荐算法以及完善的关注者模型体系,当然还有很多其他机制共同作用下的结果。本提按作为一个总纲为paopao-ce引入类似的机制,这将是一个持续完善的缓慢过程,一切都是为了用户体验,用户就是上帝,用户需要什么,paopao-ce就努力提供什么! @@ -35,6 +35,9 @@ #### 方案二 在方案一的基础上通过引入 *图数据库* 实现可选需求。待研究、设计~ +#### 设计细节 +* 参考实现(PR): +[add Followship feature #355](https://github.com/rocboss/paopao-ce/pull/355) ### 疑问 @@ -57,4 +60,7 @@ * 初始文档, 先占个位置; #### v0.1(2022-11-21) - 北野 -* 添加初始内容; \ No newline at end of file +* 添加初始内容; + +#### v1.0(2023-08-14) - 北野 +* 添加参考实现; \ No newline at end of file diff --git a/internal/conf/db.go b/internal/conf/db.go index 1fa2a0ea..32867a9c 100644 --- a/internal/conf/db.go +++ b/internal/conf/db.go @@ -25,6 +25,7 @@ const ( TableComment = "comment" TableCommentContent = "comment_content" TableCommentReply = "comment_reply" + TableFollowing = "following" TableContact = "contact" TableContactGroup = "contact_group" TableMessage = "message" diff --git a/internal/conf/setting.go b/internal/conf/setting.go index 8efff37a..ccb05cbf 100644 --- a/internal/conf/setting.go +++ b/internal/conf/setting.go @@ -305,6 +305,7 @@ func (s *databaseConf) TableNames() (res TableNameMap) { TableComment, TableCommentContent, TableCommentReply, + TableFollowing, TableContact, TableContactGroup, TableMessage, diff --git a/internal/core/core.go b/internal/core/core.go index ab0ba620..810d1e97 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -30,6 +30,7 @@ type DataService interface { // 用户服务 UserManageService ContactManageService + FollowingManageService // 安全服务 SecurityService diff --git a/internal/core/ms/user.go b/internal/core/ms/user.go index 88cc6e73..9b720a1d 100644 --- a/internal/core/ms/user.go +++ b/internal/core/ms/user.go @@ -7,10 +7,11 @@ package ms type ( ContactItem struct { UserId int64 `json:"user_id"` - UserName string `db:"username" json:"username"` + Username string `db:"username" json:"username"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` - Phone string `json:"phone"` + Phone string `json:"phone,omitempty"` + IsFollow bool `json:"is_follow,omitempty"` } ContactList struct { diff --git a/internal/core/user.go b/internal/core/user.go index 04d30e23..fa1cd9b8 100644 --- a/internal/core/user.go +++ b/internal/core/user.go @@ -26,3 +26,13 @@ type ContactManageService interface { GetContacts(userId int64, offset int, limit int) (*ms.ContactList, error) IsFriend(userID int64, friendID int64) bool } + +// FollowingManageService 关注管理服务 +type FollowingManageService interface { + FollowUser(userId int64, followId int64) error + UnfollowUser(userId int64, followId int64) error + ListFollows(userId int64, limit, offset int) (*ms.ContactList, error) + ListFollowings(userId int64, limit, offset int) (*ms.ContactList, error) + GetFollowCount(userId int64) (int64, int64, error) + IsFollow(userId int64, followId int64) bool +} diff --git a/internal/dao/jinzhu/contacts.go b/internal/dao/jinzhu/contacts.go index b44307fe..e6f20e4b 100644 --- a/internal/dao/jinzhu/contacts.go +++ b/internal/dao/jinzhu/contacts.go @@ -249,7 +249,7 @@ func (s *contactManageSrv) GetContacts(userId int64, offset int, limit int) (*ms if c.User != nil { resp.Contacts = append(resp.Contacts, ms.ContactItem{ UserId: c.FriendId, - UserName: c.User.Username, + Username: c.User.Username, Nickname: c.User.Nickname, Avatar: c.User.Avatar, Phone: c.User.Phone, diff --git a/internal/dao/jinzhu/dbr/following.go b/internal/dao/jinzhu/dbr/following.go new file mode 100644 index 00000000..9422631e --- /dev/null +++ b/internal/dao/jinzhu/dbr/following.go @@ -0,0 +1,87 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package dbr + +import ( + "github.com/sirupsen/logrus" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type Following struct { + *Model + User *User `json:"-" gorm:"foreignKey:ID;references:FollowId"` + UserId int64 `json:"user_id"` + FollowId int64 `json:"friend_id"` +} + +func (f *Following) GetFollowing(db *gorm.DB, userId, followId int64) (*Following, error) { + var following Following + err := db.Omit("User").Unscoped().Where("user_id = ? AND follow_id = ?", userId, followId).First(&following).Error + if err != nil { + logrus.Debugf("Following.GetFollowing get following error:%s", err) + return nil, err + } + return &following, nil +} + +func (f *Following) DelFollowing(db *gorm.DB, userId, followId int64) error { + return db.Omit("User").Unscoped().Where("user_id = ? AND follow_id = ?", userId, followId).Delete(f).Error +} + +func (f *Following) ListFollows(db *gorm.DB, userId int64, limit int, offset int) (res []*Following, total int64, err error) { + db = db.Model(f).Where("user_id=?", userId) + if err = db.Count(&total).Error; err != nil { + return + } + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + db.Joins("User").Order(clause.OrderByColumn{Column: clause.Column{Table: "User", Name: "nickname"}, Desc: false}) + if err = db.Find(&res).Error; err != nil { + return + } + return +} + +func (f *Following) ListFollowingIds(db *gorm.DB, userId int64, limit, offset int) (ids []int64, total int64, err error) { + db = db.Model(f).Where("follow_id=?", userId) + if err = db.Count(&total).Error; err != nil { + return + } + if offset >= 0 && limit > 0 { + db = db.Offset(offset).Limit(limit) + } + if err = db.Omit("User").Select("user_id").Find(&ids).Error; err != nil { + return + } + return +} + +func (f *Following) FollowCount(db *gorm.DB, userId int64) (follows int64, followings int64, err error) { + if err = db.Model(f).Where("user_id=?", userId).Count(&follows).Error; err != nil { + return + } + if err = db.Model(f).Where("follow_id=?", userId).Count(&followings).Error; err != nil { + return + } + return +} + +func (s *Following) IsFollow(db *gorm.DB, userId int64, followId int64) bool { + if _, err := s.GetFollowing(db, userId, followId); err == nil { + return true + } + return false +} + +func (f *Following) Create(db *gorm.DB) (*Following, error) { + err := db.Omit("User").Create(f).Error + return f, err +} + +func (c *Following) UpdateInUnscoped(db *gorm.DB) error { + return db.Unscoped().Omit("User").Save(c).Error +} diff --git a/internal/dao/jinzhu/following.go b/internal/dao/jinzhu/following.go new file mode 100644 index 00000000..86a1ae45 --- /dev/null +++ b/internal/dao/jinzhu/following.go @@ -0,0 +1,104 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package jinzhu + +import ( + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/core/ms" + "github.com/rocboss/paopao-ce/internal/dao/jinzhu/dbr" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +var ( + _ core.FollowingManageService = (*followingManageSrv)(nil) +) + +type followingManageSrv struct { + db *gorm.DB + f *dbr.Following + u *dbr.User +} + +func newFollowingManageService(db *gorm.DB) core.FollowingManageService { + return &followingManageSrv{ + db: db, + f: &dbr.Following{}, + u: &dbr.User{}, + } +} + +func (s *followingManageSrv) FollowUser(userId int64, followId int64) error { + if _, err := s.f.GetFollowing(s.db, userId, followId); err != nil { + following := &dbr.Following{ + UserId: userId, + FollowId: followId, + } + if _, err = following.Create(s.db); err != nil { + logrus.Errorf("contactManageSrv.fetchOrNewContact create new contact err:%s", err) + return err + } + } + return nil +} + +func (s *followingManageSrv) UnfollowUser(userId int64, followId int64) error { + return s.f.DelFollowing(s.db, userId, followId) +} + +func (s *followingManageSrv) ListFollows(userId int64, limit, offset int) (*ms.ContactList, error) { + follows, totoal, err := s.f.ListFollows(s.db, userId, limit, offset) + if err != nil { + return nil, err + } + res := &ms.ContactList{ + Total: totoal, + } + for _, f := range follows { + res.Contacts = append(res.Contacts, ms.ContactItem{ + UserId: f.User.ID, + Username: f.User.Username, + Nickname: f.User.Nickname, + Avatar: f.User.Avatar, + IsFollow: true, + }) + } + return res, nil +} + +func (s *followingManageSrv) ListFollowings(userId int64, limit, offset int) (*ms.ContactList, error) { + followingIds, totoal, err := s.f.ListFollowingIds(s.db, userId, limit, offset) + if err != nil { + return nil, err + } + followings, err := s.u.ListUserInfoById(s.db, followingIds) + if err != nil { + return nil, err + } + res := &ms.ContactList{ + Total: totoal, + } + for _, user := range followings { + res.Contacts = append(res.Contacts, ms.ContactItem{ + UserId: user.ID, + Username: user.Username, + Nickname: user.Nickname, + Avatar: user.Avatar, + IsFollow: s.IsFollow(userId, user.ID), + }) + } + return res, nil +} + +func (s *followingManageSrv) GetFollowCount(userId int64) (int64, int64, error) { + return s.f.FollowCount(s.db, userId) +} + +func (s *followingManageSrv) IsFollow(userId int64, followId int64) bool { + if _, err := s.f.GetFollowing(s.db, userId, followId); err == nil { + return true + } + return false +} diff --git a/internal/dao/jinzhu/gorm.go b/internal/dao/jinzhu/gorm.go index c160c579..c5b0d4f6 100644 --- a/internal/dao/jinzhu/gorm.go +++ b/internal/dao/jinzhu/gorm.go @@ -17,6 +17,7 @@ var ( _comment_ string _commentContent_ string _commentReply_ string + _following_ string _contact_ string _contactGroup_ string _message_ string @@ -42,6 +43,7 @@ func initTableName() { _comment_ = m[conf.TableComment] _commentContent_ = m[conf.TableCommentContent] _commentReply_ = m[conf.TableCommentReply] + _following_ = m[conf.TableFollowing] _contact_ = m[conf.TableContact] _contactGroup_ = m[conf.TableContactGroup] _message_ = m[conf.TableMessage] diff --git a/internal/dao/jinzhu/jinzhu.go b/internal/dao/jinzhu/jinzhu.go index 50c26768..5f8dbb15 100644 --- a/internal/dao/jinzhu/jinzhu.go +++ b/internal/dao/jinzhu/jinzhu.go @@ -42,6 +42,7 @@ type dataSrv struct { core.CommentManageService core.UserManageService core.ContactManageService + core.FollowingManageService core.SecurityService core.AttachmentCheckService } @@ -110,6 +111,7 @@ func NewDataService() (core.DataService, core.VersionInfo) { CommentManageService: newCommentManageService(db), UserManageService: newUserManageService(db), ContactManageService: newContactManageService(db), + FollowingManageService: newFollowingManageService(db), SecurityService: newSecurityService(db, pvs), AttachmentCheckService: security.NewAttachmentCheckService(), } diff --git a/internal/model/web/core.go b/internal/model/web/core.go index 587d00ce..78c04bb3 100644 --- a/internal/model/web/core.go +++ b/internal/model/web/core.go @@ -36,8 +36,8 @@ type UserInfoResp struct { Phone string `json:"phone"` IsAdmin bool `json:"is_admin"` CreatedOn int64 `json:"created_on"` - Follows int `json:"follows"` - Followings int `json:"followings"` + Follows int64 `json:"follows"` + Followings int64 `json:"followings"` } type GetUnreadMsgCountReq struct { diff --git a/internal/model/web/loose.go b/internal/model/web/loose.go index 9376a1ea..8ab19584 100644 --- a/internal/model/web/loose.go +++ b/internal/model/web/loose.go @@ -76,8 +76,8 @@ type GetUserProfileResp struct { IsFriend bool `json:"is_friend"` IsFollowing bool `json:"is_following"` CreatedOn int64 `json:"created_on"` - Follows int `json:"follows"` - Followings int `json:"followings"` + Follows int64 `json:"follows"` + Followings int64 `json:"followings"` } type TopicListReq struct { diff --git a/internal/model/web/xerror.go b/internal/model/web/xerror.go index 471196f6..f705a613 100644 --- a/internal/model/web/xerror.go +++ b/internal/model/web/xerror.go @@ -79,6 +79,11 @@ var ( ErrDeleteFriendFailed = xerror.NewError(80006, "删除好友失败") ErrGetContactsFailed = xerror.NewError(80007, "获取联系人列表失败") ErrNoActionToSelf = xerror.NewError(80008, "不允许对自己操作") + ErrFolloUserFailed = xerror.NewError(80100, "关注失败") + ErrUnfollowUserFailed = xerror.NewError(80101, "取消关注失败") + ErrListFollowsFailed = xerror.NewError(80102, "获取关注列表失败") + ErrListFollowingsFailed = xerror.NewError(80103, "获取粉丝列表列表失败") + ErrGetFollowCountFailed = xerror.NewError(80104, "获取关注计数信息失败") ErrFollowTopicFailed = xerror.NewError(90001, "关注话题失败") ErrUnfollowTopicFailed = xerror.NewError(90002, "取消关注话题失败") diff --git a/internal/servants/web/core.go b/internal/servants/web/core.go index f5a8767d..de092a60 100644 --- a/internal/servants/web/core.go +++ b/internal/servants/web/core.go @@ -60,6 +60,10 @@ func (s *coreSrv) GetUserInfo(req *web.UserInfoReq) (*web.UserInfoResp, mir.Erro if user.Model == nil || user.ID < 0 { return nil, xerror.UnauthorizedAuthNotExist } + follows, followings, err := s.Ds.GetFollowCount(user.ID) + if err != nil { + return nil, web.ErrGetFollowCountFailed + } resp := &web.UserInfoResp{ Id: user.ID, Nickname: user.Nickname, @@ -69,8 +73,8 @@ func (s *coreSrv) GetUserInfo(req *web.UserInfoReq) (*web.UserInfoResp, mir.Erro Balance: user.Balance, IsAdmin: user.IsAdmin, CreatedOn: user.CreatedOn, - Follows: 0, // TODO - Followings: 0, // TODO + Follows: follows, + Followings: followings, } if user.Phone != "" && len(user.Phone) == 11 { resp.Phone = user.Phone[0:3] + "****" + user.Phone[7:] diff --git a/internal/servants/web/followship.go b/internal/servants/web/followship.go index 2c399f02..dd5efb1b 100644 --- a/internal/servants/web/followship.go +++ b/internal/servants/web/followship.go @@ -8,10 +8,10 @@ import ( "github.com/alimy/mir/v4" "github.com/gin-gonic/gin" api "github.com/rocboss/paopao-ce/auto/api/v1" - "github.com/rocboss/paopao-ce/internal/core/ms" "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/chain" + "github.com/sirupsen/logrus" ) var ( @@ -28,27 +28,49 @@ func (s *followshipSrv) Chain() gin.HandlersChain { } func (s *followshipSrv) ListFollowings(r *web.ListFollowingsReq) (*web.ListFollowingsResp, mir.Error) { - // TODO - res := ms.ContactList{} + he, err := s.Ds.GetUserByUsername(r.Username) + if err != nil { + logrus.Errorf("Ds.GetUserByUsername err: %s", err) + return nil, web.ErrNoExistUsername + } + res, err := s.Ds.ListFollowings(he.ID, r.PageSize, r.Page-1) + if err != nil { + logrus.Errorf("Ds.ListFollowings err: %s", err) + return nil, web.ErrListFollowingsFailed + } resp := base.PageRespFrom(res.Contacts, r.Page, r.PageSize, res.Total) return (*web.ListFollowingsResp)(resp), nil } func (s *followshipSrv) ListFollows(r *web.ListFollowsReq) (*web.ListFollowsResp, mir.Error) { - // TODO - res := ms.ContactList{} + he, err := s.Ds.GetUserByUsername(r.Username) + if err != nil { + logrus.Errorf("Ds.GetUserByUsername err: %s", err) + return nil, web.ErrNoExistUsername + } + res, err := s.Ds.ListFollows(he.ID, r.PageSize, r.Page-1) + if err != nil { + logrus.Errorf("Ds.ListFollows err: %s", err) + return nil, web.ErrListFollowsFailed + } resp := base.PageRespFrom(res.Contacts, r.Page, r.PageSize, res.Total) return (*web.ListFollowsResp)(resp), nil } func (s *followshipSrv) UnfollowUser(r *web.UnfollowUserReq) mir.Error { - // TODO - return web.ErrNotImplemented + if err := s.Ds.UnfollowUser(r.User.ID, r.UserId); err != nil { + logrus.Errorf("Ds.UnfollowUser err: %s userId: %d followId: %d", err, r.User.ID, r.UserId) + return web.ErrUnfollowUserFailed + } + return nil } func (s *followshipSrv) FollowUser(r *web.FollowUserReq) mir.Error { - // TODO - return web.ErrNotImplemented + if err := s.Ds.FollowUser(r.User.ID, r.UserId); err != nil { + logrus.Errorf("Ds.FollowUser err: %s userId: %d followId: %d", err, r.User.ID, r.UserId) + return web.ErrUnfollowUserFailed + } + return nil } func newFollowshipSrv(s *base.DaoServant) api.Followship { diff --git a/internal/servants/web/loose.go b/internal/servants/web/loose.go index 6096538b..27f33f77 100644 --- a/internal/servants/web/loose.go +++ b/internal/servants/web/loose.go @@ -177,17 +177,26 @@ func (s *looseSrv) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfi if req.User != nil && req.User.ID != he.ID { isFriend = s.Ds.IsFriend(req.User.ID, he.ID) } + isFollowing := false + if req.User != nil { + isFollowing = s.Ds.IsFollow(req.User.ID, he.ID) + } + follows, followings, err := s.Ds.GetFollowCount(he.ID) + if err != nil { + return nil, web.ErrGetPostsFailed + } return &web.GetUserProfileResp{ - ID: he.ID, - Nickname: he.Nickname, - Username: he.Username, - Status: he.Status, - Avatar: he.Avatar, - IsAdmin: he.IsAdmin, - IsFriend: isFriend, - CreatedOn: he.CreatedOn, - Follows: 0, // TODO - Followings: 0, // TODO + ID: he.ID, + Nickname: he.Nickname, + Username: he.Username, + Status: he.Status, + Avatar: he.Avatar, + IsAdmin: he.IsAdmin, + IsFriend: isFriend, + IsFollowing: isFollowing, + CreatedOn: he.CreatedOn, + Follows: follows, + Followings: followings, }, nil } diff --git a/scripts/migration/mysql/0010_user_following.down.sql b/scripts/migration/mysql/0010_user_following.down.sql new file mode 100644 index 00000000..48e42956 --- /dev/null +++ b/scripts/migration/mysql/0010_user_following.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `p_following`; diff --git a/scripts/migration/mysql/0010_user_following.up.sql b/scripts/migration/mysql/0010_user_following.up.sql new file mode 100644 index 00000000..fcccdb00 --- /dev/null +++ b/scripts/migration/mysql/0010_user_following.up.sql @@ -0,0 +1,11 @@ +CREATE TABLE `p_following` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint unsigned NOT NULL, + `follow_id` bigint unsigned NOT NULL, + `is_del` tinyint NOT NULL DEFAULT 0, -- 是否删除, 0否, 1是 + `created_on` bigint unsigned NOT NULL DEFAULT '0', + `modified_on` bigint unsigned NOT NULL DEFAULT '0', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_following_user_follow` (`user_id`,`follow_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; diff --git a/scripts/migration/postgres/0009_user_following.down.sql b/scripts/migration/postgres/0009_user_following.down.sql new file mode 100644 index 00000000..d16a02f8 --- /dev/null +++ b/scripts/migration/postgres/0009_user_following.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS p_following; diff --git a/scripts/migration/postgres/0009_user_following.up.sql b/scripts/migration/postgres/0009_user_following.up.sql new file mode 100644 index 00000000..8ce887b0 --- /dev/null +++ b/scripts/migration/postgres/0009_user_following.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE p_following ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + follow_id BIGINT NOT NULL, + is_del SMALLINT NOT NULL DEFAULT 0, -- 是否删除, 0否, 1是 + created_on BIGINT NOT NULL DEFAULT 0, + modified_on BIGINT NOT NULL DEFAULT 0, + deleted_on BIGINT NOT NULL DEFAULT 0 +); +CREATE INDEX idx_following_user_follow ON p_following USING btree (user_id, follow_id); diff --git a/scripts/migration/sqlite3/0010_user_following.down.sql b/scripts/migration/sqlite3/0010_user_following.down.sql new file mode 100644 index 00000000..c5040e81 --- /dev/null +++ b/scripts/migration/sqlite3/0010_user_following.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS "p_following"; diff --git a/scripts/migration/sqlite3/0010_user_following.up.sql b/scripts/migration/sqlite3/0010_user_following.up.sql new file mode 100644 index 00000000..105ac710 --- /dev/null +++ b/scripts/migration/sqlite3/0010_user_following.up.sql @@ -0,0 +1,15 @@ +CREATE TABLE "p_following" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "follow_id" integer NOT NULL, + "is_del" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX "idx_following_user_follow" +ON "p_following" ( + "user_id" ASC, + "follow_id" ASC +); diff --git a/scripts/paopao-mysql.sql b/scripts/paopao-mysql.sql index 1e2401c7..dc758513 100644 --- a/scripts/paopao-mysql.sql +++ b/scripts/paopao-mysql.sql @@ -318,6 +318,22 @@ CREATE TABLE `p_user` ( KEY `idx_user_phone` (`phone`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=100058 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户'; +-- ---------------------------- +-- Table structure for p_following +-- ---------------------------- +DROP TABLE IF EXISTS `p_following`; +CREATE TABLE `p_following` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint unsigned NOT NULL, + `follow_id` bigint unsigned NOT NULL, + `is_del` tinyint NOT NULL DEFAULT 0, -- 是否删除, 0否, 1是 + `created_on` bigint unsigned NOT NULL DEFAULT '0', + `modified_on` bigint unsigned NOT NULL DEFAULT '0', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_following_user_follow` (`user_id`,`follow_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + -- ---------------------------- -- Table structure for p_contact -- ---------------------------- diff --git a/scripts/paopao-postgres.sql b/scripts/paopao-postgres.sql index c21fc2ac..93e15c20 100644 --- a/scripts/paopao-postgres.sql +++ b/scripts/paopao-postgres.sql @@ -265,6 +265,18 @@ CREATE TABLE p_user ( CREATE UNIQUE INDEX idx_user_username ON p_user USING btree (username); CREATE INDEX idx_user_phone ON p_user USING btree (phone); +DROP TABLE IF EXISTS p_following; +CREATE TABLE p_following ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + follow_id BIGINT NOT NULL, + is_del SMALLINT NOT NULL DEFAULT 0, -- 是否删除, 0否, 1是 + created_on BIGINT NOT NULL DEFAULT 0, + modified_on BIGINT NOT NULL DEFAULT 0, + deleted_on BIGINT NOT NULL DEFAULT 0 +); +CREATE INDEX idx_following_user_follow ON p_following USING btree (user_id, follow_id); + DROP TABLE IF EXISTS p_contact; CREATE TABLE p_contact ( id BIGSERIAL PRIMARY KEY, diff --git a/scripts/paopao-sqlite3.sql b/scripts/paopao-sqlite3.sql index fc17748e..7655f3a5 100644 --- a/scripts/paopao-sqlite3.sql +++ b/scripts/paopao-sqlite3.sql @@ -113,6 +113,21 @@ CREATE TABLE "p_tweet_comment_thumbs" ( "is_del" integer NOT NULL DEFAULT 0 -- 是否删除 0 为未删除、1 为已删除 ); +-- ---------------------------- +-- Table structure for p_following +-- ---------------------------- +DROP TABLE IF EXISTS "p_following"; +CREATE TABLE "p_following" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "follow_id" integer NOT NULL, + "is_del" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + PRIMARY KEY ("id") +); + -- ---------------------------- -- Table structure for p_contact -- ---------------------------- @@ -464,6 +479,15 @@ ON "p_tweet_comment_thumbs"( "tweet_id" ASC ); +-- ---------------------------- +-- Indexes structure for table p_following +-- ---------------------------- +CREATE INDEX "idx_following_user_follow" +ON "p_following" ( + "user_id" ASC, + "follow_id" ASC +); + -- ---------------------------- -- Indexes structure for table p_contact -- ----------------------------