From 14cd9a134dce574859d7c69704dfafbeff879ee4 Mon Sep 17 00:00:00 2001 From: Chuan-Heng Hsiao Date: Tue, 24 Sep 2024 18:45:56 -0400 Subject: [PATCH] add read-write-permission for user-board and user-article. 1. simplify schema to UserBoard and UserArticle 2. api.UserBoardPerm 3. api.UserArticlePerm 4. board.ParentID --- api/add_favorite_board.go | 4 +- api/add_favorite_board_test.go | 13 +- api/article_utils.go | 12 +- api/board_utils.go | 8 +- api/create_article.go | 5 + api/create_article_test.go | 7 +- api/create_board.go | 5 + api/create_board_test.go | 2 + api/create_comment.go | 5 + api/create_comment_test.go | 2 + api/delete_articles.go | 23 +- api/delete_articles_test.go | 2 + api/delete_comments_test.go | 2 + api/edit_article.go | 6 + api/edit_article_test.go | 2 + api/errors.go | 64 +++- api/get_article_blocks.go | 6 +- api/get_article_blocks_test.go | 2 + api/get_article_detail.go | 6 + api/get_article_detail_test.go | 34 +- api/get_board_detail.go | 4 +- api/get_board_detail_test.go | 3 + api/get_board_summary.go | 5 + api/get_board_summary_test.go | 7 +- api/get_user_info.go | 45 ++- api/get_user_info_test.go | 3 + api/get_user_visit_count.go | 1 + api/load_article_comments.go | 4 +- api/load_article_comments_test.go | 6 + api/load_auto_complete_boards_test.go | 10 +- api/load_bottom_articles.go | 30 +- api/load_bottom_articles_test.go | 20 +- api/load_class_boards.go | 2 +- api/load_class_boards_test.go | 11 +- api/load_general_articles.go | 38 +- api/load_general_articles_by_keyword.go | 65 +--- api/load_general_articles_test.go | 46 ++- api/load_general_boards_by_class_test.go | 10 +- api/load_general_boards_test.go | 8 +- api/load_popular_boards_test.go | 10 +- api/load_user_articles.go | 33 +- api/load_user_articles_test.go | 8 +- api/load_user_comments.go | 60 +-- api/load_user_comments_test.go | 18 +- api/reply_comments.go | 6 + api/reply_comments_test.go | 6 + api/testcases_test.go | 120 ++++-- api/testinit.go | 6 +- api/user_article_perm.go | 224 +++++++++++ api/user_board_perm.go | 434 ++++++++++++++++++++++ apitypes/article_summary.go | 3 + apitypes/errors.go | 5 +- cron/load_article_details.go | 12 +- cron/load_general_articles.go | 4 +- cron/load_general_articles_test.go | 42 ++- cron/load_man_articles.go | 4 +- dbcs/testcases_test.go | 38 +- fav/fav_test.go | 22 +- mockhttp/load_general_articles2.go | 4 +- pttbbs | 2 +- queue/testcases_test.go | 38 +- schema/article.go | 29 +- schema/article_content_info.go | 13 +- schema/article_detail_summary.go | 12 +- schema/article_detail_summary_test.go | 36 +- schema/article_perm_info.go | 57 +++ schema/article_summary.go | 24 +- schema/article_summary_test.go | 60 ++- schema/article_summary_with_regex.go | 33 +- schema/article_summary_with_regex_test.go | 6 +- schema/board.go | 6 +- schema/board_detail.go | 10 +- schema/board_perm_info.go | 53 +++ schema/board_summary.go | 6 + schema/errors.go | 2 + schema/init.go | 18 +- schema/man_article_summary.go | 2 - schema/testinit.go | 4 +- schema/user.go | 20 + schema/user_article.go | 77 ++++ schema/user_board.go | 107 ++++++ schema/user_board_test.go | 95 +++++ schema/user_favorites_test.go | 2 +- schema/user_info_summary.go | 6 +- schema/user_new_info.go | 5 +- schema/user_nickname.go | 7 +- schema/user_perm_info.go | 44 +++ schema/user_posttime.go | 33 ++ schema/user_read_article.go | 79 ++-- schema/user_read_article_delete.go | 27 -- schema/user_read_article_delete_test.go | 52 --- schema/user_read_article_test.go | 52 +-- schema/user_read_board.go | 58 ++- schema/user_read_board_test.go | 110 +++--- types/const.go | 2 + types/types.go | 21 ++ 96 files changed, 2132 insertions(+), 663 deletions(-) create mode 100644 api/user_article_perm.go create mode 100644 api/user_board_perm.go create mode 100644 schema/article_perm_info.go create mode 100644 schema/board_perm_info.go create mode 100644 schema/user_article.go create mode 100644 schema/user_board.go create mode 100644 schema/user_board_test.go create mode 100644 schema/user_perm_info.go create mode 100644 schema/user_posttime.go delete mode 100644 schema/user_read_article_delete.go delete mode 100644 schema/user_read_article_delete_test.go diff --git a/api/add_favorite_board.go b/api/add_favorite_board.go index 71737573..2e469ddd 100644 --- a/api/add_favorite_board.go +++ b/api/add_favorite_board.go @@ -48,9 +48,9 @@ func AddFavoriteBoard(remoteAddr string, userID bbs.UUserID, params interface{}, return nil, 500, err } - _, statusCode, err = isBoardValidUser(boardID, c) + _, err = CheckUserBoardPermReadable(userID, boardID) if err != nil { - return nil, statusCode, err + return nil, 403, err } bid, _, err := boardID.ToRaw() diff --git a/api/add_favorite_board_test.go b/api/add_favorite_board_test.go index 7ddc14a1..746b2f41 100644 --- a/api/add_favorite_board_test.go +++ b/api/add_favorite_board_test.go @@ -9,17 +9,28 @@ import ( "github.com/Ptt-official-app/go-pttbbs/testutil" "github.com/Ptt-official-app/go-pttbbsweb/apitypes" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" ) func TestAddFavoriteBoard(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} + _, _, err := deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) + if err != nil { + logrus.Errorf("TestAddFavoriteBoard: unable to deserializeBoardsAndUpdateDB: e: %v", err) + } + paramsLoad0 := &LoadGeneralBoardsParams{ StartIdx: "vFSt-Q@WhoAmI", } - _, _, _ = LoadGeneralBoardsByClass("localhost", "SYSOP", paramsLoad0, nil) + result_i, _, err := LoadGeneralBoardsByClass("localhost", "SYSOP", paramsLoad0, nil) + result, _ := result_i.(*LoadGeneralBoardsResult) + logrus.Infof("TestAddFavoriteBoard: after LoadGeneralBoardsByClass: result: %v", result.List[0]) params0 := &AddFavoriteBoardParams{ FBoardID: "WhoAmI", diff --git a/api/article_utils.go b/api/article_utils.go index f7661752..6fd82b96 100644 --- a/api/article_utils.go +++ b/api/article_utils.go @@ -91,9 +91,9 @@ func deserializeArticlesAndUpdateDB(userID bbs.UUserID, bboardID bbs.BBoardID, a for _, each_b := range articleSummaries_b { if each_b.Read { each_db := &schema.UserReadArticle{ - UserID: userID, - ArticleID: each_b.ArticleID, - UpdateNanoTS: updateNanoTS, + UserID: userID, + ArticleID: each_b.ArticleID, + ReadUpdateNanoTS: updateNanoTS, } userReadArticles = append(userReadArticles, each_db) @@ -461,9 +461,9 @@ func setUserReadArticle(content [][]*types.Rune, userID bbs.UUserID, articleID b // user read article userReadArticle := &schema.UserReadArticle{ - UserID: userID, - ArticleID: articleID, - UpdateNanoTS: updateNanoTS, + UserID: userID, + ArticleID: articleID, + ReadUpdateNanoTS: updateNanoTS, } _ = schema.UpdateUserReadArticle(userReadArticle) } diff --git a/api/board_utils.go b/api/board_utils.go index edd7a110..b1b30d5f 100644 --- a/api/board_utils.go +++ b/api/board_utils.go @@ -70,9 +70,9 @@ func deserializeBoardsAndUpdateDB(userID bbs.UUserID, boardSummaries_b []*bbs.Bo if each_b.Read { each_db := &schema.UserReadBoard{ - UserID: userID, - BBoardID: each_b.BBoardID, - UpdateNanoTS: updateNanoTS, + UserID: userID, + BBoardID: each_b.BBoardID, + ReadUpdateNanoTS: updateNanoTS, } userReadBoards = append(userReadBoards, each_db) } @@ -274,7 +274,7 @@ func checkUserReadBoard(userID bbs.UUserID, userBoardInfoMap map[bbs.BBoardID]*a // the Read flag is set based on the existing db.UpdateNanoTS for _, each := range dbResults { eachBoardID := each.BBoardID - eachReadNanoTS := each.UpdateNanoTS + eachReadNanoTS := each.ReadUpdateNanoTS eachBoardInfo, ok := userBoardInfoMap[eachBoardID] if !ok { diff --git a/api/create_article.go b/api/create_article.go index 8f78b160..144f431c 100644 --- a/api/create_article.go +++ b/api/create_article.go @@ -48,6 +48,11 @@ func CreateArticle(remoteAddr string, userID bbs.UUserID, params interface{}, pa return nil, 500, err } + err = CheckUserBoardPermPostable(userID, boardID) + if err != nil { + return nil, 403, err + } + theType := types.Utf8ToBig5(theParams.PostType) theTitle := types.Utf8ToBig5(theParams.Title) content := simplifyContent(theParams.Content) diff --git a/api/create_article_test.go b/api/create_article_test.go index e201fcc6..740255b8 100644 --- a/api/create_article_test.go +++ b/api/create_article_test.go @@ -16,8 +16,13 @@ func TestCreateArticle(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} - _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) + _, _, err := deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) + if err != nil { + logrus.Errorf("TestCreateArticle: unable to deserializeBoardsAndUpdateDB: e: %v", err) + } path0 := &CreateArticlePath{ FBoardID: "WhoAmI", diff --git a/api/create_board.go b/api/create_board.go index 05f97ea6..32861ebe 100644 --- a/api/create_board.go +++ b/api/create_board.go @@ -49,6 +49,11 @@ func CreateBoard(remoteAddr string, userID bbs.UUserID, params interface{}, path return nil, 400, ErrInvalidPath } + err = CheckUserBoardPermCreatable(userID) + if err != nil { + return nil, 403, err + } + theClass := types.Utf8ToBig5(theParams.BrdClass) theTitle := types.Utf8ToBig5(theParams.BrdTitle) diff --git a/api/create_board_test.go b/api/create_board_test.go index 8ca58c03..f336a7e3 100644 --- a/api/create_board_test.go +++ b/api/create_board_test.go @@ -15,6 +15,8 @@ func TestCreateBoard(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + path0 := &CreateBoardPath{ ClsBid: 2, } diff --git a/api/create_comment.go b/api/create_comment.go index e1084a1e..67649125 100644 --- a/api/create_comment.go +++ b/api/create_comment.go @@ -48,6 +48,11 @@ func CreateComment(remoteAddr string, userID bbs.UUserID, params interface{}, pa } articleID := thePath.FArticleID.ToArticleID() + err = CheckUserBoardPermPostable(userID, boardID) + if err != nil { + return nil, 403, err + } + // content-dbcs contentDBCS := types.Utf8ToBig5(theParams.Content) diff --git a/api/create_comment_test.go b/api/create_comment_test.go index e1a4697b..df10958e 100644 --- a/api/create_comment_test.go +++ b/api/create_comment_test.go @@ -16,6 +16,8 @@ func TestCreateComment(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) diff --git a/api/delete_articles.go b/api/delete_articles.go index a0443c57..8217e670 100644 --- a/api/delete_articles.go +++ b/api/delete_articles.go @@ -49,11 +49,29 @@ func DeleteArticles(remoteAddr string, userID bbs.UUserID, params interface{}, p return nil, 500, err } + userBoardPermReadable, err := CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return nil, 403, err + } + var articleIDs []bbs.ArticleID for _, articleID := range theParams.ArticleIDs { articleIDs = append(articleIDs, articleID.ToArticleID()) } + articlePermMap, err := CheckUserArticlesPermDeletable(userID, boardID, articleIDs, userBoardPermReadable) + if err != nil { + return nil, 500, err + } + articleIDs = make([]bbs.ArticleID, 0, len(articleIDs)) + for articleID, eachErr := range articlePermMap { + if eachErr != nil { + continue + } + + articleIDs = append(articleIDs, articleID) + } + // to go-pttbbs theParams_b := &pttbbsapi.DeleteArticlesParams{ ArticleIDs: articleIDs, @@ -85,11 +103,6 @@ func DeleteArticles(remoteAddr string, userID bbs.UUserID, params interface{}, p if err != nil { return nil, 500, err } - err = schema.DeleteUserReadArticles(boardID, result_b.ArticleIDs, updateNanoTS) - if err != nil { - return nil, 500, err - } - result = &DeleteArticlesResult{ Success: true, TokenUser: userID, diff --git a/api/delete_articles_test.go b/api/delete_articles_test.go index 74a03b2e..945a238f 100644 --- a/api/delete_articles_test.go +++ b/api/delete_articles_test.go @@ -15,6 +15,8 @@ func TestDeleteArticles(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) diff --git a/api/delete_comments_test.go b/api/delete_comments_test.go index e7cf8499..e8b34c9c 100644 --- a/api/delete_comments_test.go +++ b/api/delete_comments_test.go @@ -17,6 +17,8 @@ func TestDeleteComments(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummarySYSOP_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) diff --git a/api/edit_article.go b/api/edit_article.go index 0748ad53..494818eb 100644 --- a/api/edit_article.go +++ b/api/edit_article.go @@ -58,6 +58,12 @@ func EditArticleDetail(remoteAddr string, userID bbs.UUserID, params interface{} } articleID := thePath.FArticleID.ToArticleID() + // check permission + err = CheckUserArticlePermEditable(userID, boardID, articleID, true) + if err != nil { + return nil, 403, err + } + _, oldContentPrefix, oldSignatureDBCS, _, oldSZ, oldsum, statusCode, err := editArticleGetArticleContentInfo(userID, boardID, articleID, c, false) if err != nil { return nil, statusCode, err diff --git a/api/edit_article_test.go b/api/edit_article_test.go index 34f008fe..1163ff7f 100644 --- a/api/edit_article_test.go +++ b/api/edit_article_test.go @@ -14,6 +14,8 @@ func TestEditArticleDetail(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) diff --git a/api/errors.go b/api/errors.go index cfcba0ec..c4104e05 100644 --- a/api/errors.go +++ b/api/errors.go @@ -1,6 +1,11 @@ package api -import "errors" +import ( + "errors" + "fmt" + + "github.com/Ptt-official-app/go-pttbbsweb/types" +) var ( ErrInvalidRemoteAddr = errors.New("invalid remote addr") @@ -12,10 +17,13 @@ var ( ErrInvalidToken = errors.New("invalid token") ErrInvalidOrigin = errors.New("invalid origin") ErrInvalidBackendStatusCode = errors.New("invalid backend status code") + ErrNoUser = errors.New("no user") ErrNoBoard = errors.New("no board") ErrNoArticle = errors.New("no article") - ErrAlreadyDeleted = errors.New("already deleted") - ErrFileNotFound = errors.New("file not found") + ErrNoUserBoard = errors.New("no user board") + + ErrAlreadyDeleted = errors.New("already deleted") + ErrFileNotFound = errors.New("file not found") ErrInvalidUser = errors.New("invalid user") ErrInvalidClient = errors.New("invalid client") @@ -23,4 +31,54 @@ var ( ErrAlreadyExists = errors.New("already exists") ErrInvalidFav = errors.New("invalid fav") + + ErrNotFriend = errors.New("not friend") + ErrBoardBlocked = errors.New("board blocked") + ErrBoardReported = errors.New("board reported") + ErrBoardBucket = errors.New("board bucket") + + ErrPermBoardCreatePermission = errors.New("no board create permission") + + ErrPermBoardReadHidden = errors.New("hidden board") + ErrPermBoardReadBlocked = errors.New("blocked board") + ErrPermBoardReadReported = errors.New("reported board") + + ErrPermBoardReadNotOver18 = errors.New("not over18") + ErrPermBoardReadPermission = errors.New("no board read permission") + + ErrPermPostReadOnly = errors.New("read only") + ErrPermPostBannedByBoard = errors.New("banned by board") + ErrPermBoardPostPost = errors.New("no user post permission") + ErrPermBoardPostRestricted = errors.New("only board friends") + ErrPermBoardPostViolateLaw = errors.New("violate law") + ErrPermBoardPostPermission = errors.New("no board post permission") + + ErrPermBoardPostLoginDays = errors.New("invalid login days") + ErrPermBoardPostPostLimit = errors.New("reached post limit") + + ErrPermBoardEditPermission = errors.New("no board edit permission") ) + +func ErrBoardCooldown(diffNanoTS types.NanoTS) error { + diffTS := diffNanoTS.ToTime8() + diffMin := diffTS / 60 + diffSec := diffTS % 60 + + return fmt.Errorf("board cooldown %v:%02d", diffMin, diffSec) +} + +func ErrBoardPosttime(diffNanoTS types.NanoTS) error { + diffTS := diffNanoTS.ToTime8() + diffMin := diffTS / 60 + diffSec := diffTS % 60 + + return fmt.Errorf("board posttime %v:%02d", diffMin, diffSec) +} + +func ErrFloodReject(diffNanoTS types.NanoTS) error { + diffTS := diffNanoTS.ToTime8() + diffMin := diffTS / 60 + diffSec := diffTS % 60 + + return fmt.Errorf("flood reject %v:%02d", diffMin, diffSec) +} diff --git a/api/get_article_blocks.go b/api/get_article_blocks.go index 3aa269b5..2fd69c76 100644 --- a/api/get_article_blocks.go +++ b/api/get_article_blocks.go @@ -76,10 +76,10 @@ func GetArticleBlocks(remoteAddr string, userID bbs.UUserID, params interface{}, } articleID := thePath.FArticleID.ToArticleID() - // validate user - _, statusCode, err = isBoardValidUser(boardID, c) + // check permission + err = CheckUserArticlePermReadable(userID, boardID, articleID, true) if err != nil { - return nil, statusCode, err + return nil, 403, err } if theParams.StartIdx == "" { diff --git a/api/get_article_blocks_test.go b/api/get_article_blocks_test.go index 031edb6a..d72f7fe0 100644 --- a/api/get_article_blocks_test.go +++ b/api/get_article_blocks_test.go @@ -18,6 +18,8 @@ func TestGetArticleBlocks(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) diff --git a/api/get_article_detail.go b/api/get_article_detail.go index 23136c2f..5b81ba11 100644 --- a/api/get_article_detail.go +++ b/api/get_article_detail.go @@ -68,6 +68,12 @@ func GetArticleDetail(remoteAddr string, userID bbs.UUserID, params interface{}, } articleID := thePath.FArticleID.ToArticleID() + // check permission + err = CheckUserArticlePermReadable(userID, boardID, articleID, true) + if err != nil { + return nil, 403, err + } + // validate user _, statusCode, err = isBoardValidUser(boardID, c) if err != nil { diff --git a/api/get_article_detail_test.go b/api/get_article_detail_test.go index 39836f18..a3f890d5 100644 --- a/api/get_article_detail_test.go +++ b/api/get_article_detail_test.go @@ -19,9 +19,15 @@ func TestGetArticleDetail(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) + articleSummaries_b := []*bbs.ArticleSummary{testArticleSummary1_b, testArticleSummary2_b} + _, _, _ = deserializeArticlesAndUpdateDB("SYSOP", "10_WhoAmI", articleSummaries_b, 123456892000000000) + params := &GetArticleDetailParams{} path0 := &GetArticleDetailPath{ FBoardID: apitypes.FBoardID("WhoAmI"), @@ -32,6 +38,7 @@ func TestGetArticleDetail(t *testing.T) { BBoardID: apitypes.FBoardID("WhoAmI"), ArticleID: apitypes.FArticleID("M.1608386280.A.BC9"), Owner: bbs.UUserID("SYSOP"), + Nickname: "神", CreateTime: types.Time8(1608386280), MTime: types.Time8(1608386280), @@ -53,8 +60,10 @@ func TestGetArticleDetail(t *testing.T) { } expectedArticleDetailSummary0 := &schema.ArticleDetailSummary{ - BBoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("1VtWRel9"), + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VtWRel9"), + BoardArticleID: "10_WhoAmI:1VtWRel9", + ContentMD5: "L6QISYJFt-Y5g4Thl-roaw", // ContentMTime: types.NanoTS(1608386280000000000), FirstCommentsLastTime: types.NanoTS(0), @@ -69,12 +78,14 @@ func TestGetArticleDetail(t *testing.T) { IP: "172.22.0.1", BBS: "批踢踢 docker(pttdocker.test)", - Idx: "1234567890@1VtWRel9", + Idx: "1608386280@1VtWRel9", } expectedArticleDetailSummary02 := &schema.ArticleDetailSummary{ - BBoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("1VtWRel9"), + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VtWRel9"), + BoardArticleID: "10_WhoAmI:1VtWRel9", + ContentMD5: "L6QISYJFt-Y5g4Thl-roaw", // ContentMTime: types.NanoTS(1608386300000000000), FirstCommentsLastTime: types.NanoTS(0), @@ -89,7 +100,7 @@ func TestGetArticleDetail(t *testing.T) { IP: "172.22.0.1", BBS: "批踢踢 docker(pttdocker.test)", - Idx: "1234567890@1VtWRel9", + Idx: "1608386280@1VtWRel9", } path1 := &GetArticleDetailPath{ @@ -100,7 +111,8 @@ func TestGetArticleDetail(t *testing.T) { expectedResult1 := &GetArticleDetailResult{ BBoardID: apitypes.FBoardID("WhoAmI"), ArticleID: apitypes.FArticleID("M.1607937174.A.081"), - Owner: bbs.UUserID("SYSOP"), + Owner: bbs.UUserID("teemo"), + Nickname: "", CreateTime: types.Time8(1607937174), MTime: types.Time8(1607937100), @@ -122,11 +134,13 @@ func TestGetArticleDetail(t *testing.T) { } expectedArticleDetailSummary1 := &schema.ArticleDetailSummary{ - BBoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("1VrooM21"), + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VrooM21"), + BoardArticleID: "10_WhoAmI:1VrooM21", + // ContentMTime: types.NanoTS(1608388624000000000), ContentMD5: "riiRuKCZzG0gAGpQiq4GJA", - Owner: "SYSOP", + Owner: "teemo", FirstCommentsMD5: "3fjMk__1yvzpuEgq8jfdmg", NComments: 0, diff --git a/api/get_board_detail.go b/api/get_board_detail.go index 906dc41c..d25feb85 100644 --- a/api/get_board_detail.go +++ b/api/get_board_detail.go @@ -44,9 +44,9 @@ func GetBoardDetail(remoteAddr string, userID bbs.UUserID, params interface{}, p } // is board-valid-user - _, statusCode, err = isBoardValidUser(boardID, c) + _, err = CheckUserBoardPermReadable(userID, boardID) if err != nil { - return nil, statusCode, err + return nil, 403, err } // fields diff --git a/api/get_board_detail_test.go b/api/get_board_detail_test.go index f36994ed..e7f89b92 100644 --- a/api/get_board_detail_test.go +++ b/api/get_board_detail_test.go @@ -14,6 +14,9 @@ func TestGetBoardDetail(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + LoadAutoCompleteBoards("", "SYSOP", NewLoadAutoCompleteBoardsParams(), nil) params0 := &GetBoardDetailParams{} diff --git a/api/get_board_summary.go b/api/get_board_summary.go index f3b383af..7fe55e95 100644 --- a/api/get_board_summary.go +++ b/api/get_board_summary.go @@ -36,6 +36,11 @@ func GetBoardSummary(remoteAddr string, userID bbs.UUserID, params interface{}, return nil, 400, err } + _, err = CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return nil, 403, err + } + // backend get-board-summary theParams_b := &pttbbsapi.LoadBoardSummaryParams{} diff --git a/api/get_board_summary_test.go b/api/get_board_summary_test.go index 37be8e0d..b05543a9 100644 --- a/api/get_board_summary_test.go +++ b/api/get_board_summary_test.go @@ -17,11 +17,14 @@ func TestGetBoardSummary(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + LoadAutoCompleteBoards("", "SYSOP", NewLoadAutoCompleteBoardsParams(), nil) - update0 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "1_test1", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update0 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "1_test1", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - _, _ = schema.UserReadBoard_c.Update(update0, update0) + _, _ = schema.UserBoard_c.Update(update0, update0) params0 := &GetBoardSummaryParams{} path0 := &GetBoardSummaryPath{FBoardID: "test1"} diff --git a/api/get_user_info.go b/api/get_user_info.go index 74cda6d4..b51452e2 100644 --- a/api/get_user_info.go +++ b/api/get_user_info.go @@ -120,6 +120,14 @@ func GetUserInfo(remoteAddr string, userID bbs.UUserID, params interface{}, path } func tryGetUserInfo(userID bbs.UUserID, queryUserID bbs.UUserID, c *gin.Context) (result *GetUserInfoResult, statusCode int, err error) { + userPermInfo, err := schema.GetUserPermInfo(userID) + if err != nil { + return nil, 500, err + } + if userPermInfo == nil { + return nil, 500, ErrInvalidUser + } + updateNanoTS := types.NowNanoTS() // get backend data @@ -155,12 +163,12 @@ func tryGetUserInfo(userID bbs.UUserID, queryUserID bbs.UUserID, c *gin.Context) return nil, 500, err } - result = NewUserInfoResult(userDetail, userNewInfo, userIDEmail, userEmail, userID) + result = NewUserInfoResult(userDetail, userNewInfo, userIDEmail, userEmail, userPermInfo) return result, 200, nil } -func NewUserInfoResult(userDetail_db *schema.UserDetail, userNewInfo_db *schema.UserNewInfo, userIDEmail_db *schema.UserIDEmail, userEmail_db *schema.UserEmail, userID bbs.UUserID) (result *GetUserInfoResult) { +func NewUserInfoResult(userDetail_db *schema.UserDetail, userNewInfo_db *schema.UserNewInfo, userIDEmail_db *schema.UserIDEmail, userEmail_db *schema.UserEmail, userPermInfo *schema.UserPermInfo) (result *GetUserInfoResult) { if userNewInfo_db == nil { userNewInfo_db = &schema.UserNewInfo{} } @@ -172,6 +180,37 @@ func NewUserInfoResult(userDetail_db *schema.UserDetail, userNewInfo_db *schema. userEmail_db = &schema.UserEmail{} } + isAllInfo := false + if userPermInfo.UserID == userDetail_db.UserID { + isAllInfo = true + } + if userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_SYSOP | ptttype.PERM_ACCOUNTS | ptttype.PERM_ACCTREG) { + isAllInfo = true + } + + if !isAllInfo { + userIDEmail_db = &schema.UserIDEmail{} + userEmail_db = &schema.UserEmail{} + + userDetail_db.Uflag = 0 + userDetail_db.PttEmail = "" + + if userDetail_db.Justify != "" { + userDetail_db.Justify = "(通過認證)" + } + + userDetail_db.PagerUIType = 0 + userDetail_db.Pager = ptttype.PAGER_OFF + + userDetail_db.LoginView = 0 + userDetail_db.UaVersion = 0 + + userDetail_db.MyAngel = "" + + userDetail_db.TimeRemoveBadPost = 0 + userDetail_db.TimeViolateLaw = 0 + } + result = &GetUserInfoResult{ UserID: userDetail_db.UserID, Username: userDetail_db.Username, @@ -250,7 +289,7 @@ func NewUserInfoResult(userDetail_db *schema.UserDetail, userNewInfo_db *schema. IDEmailTS: userIDEmail_db.UpdateNanoTS.ToTime8(), IDEmailSet: userIDEmail_db.IsSet, - TokenUser: userID, + TokenUser: userPermInfo.UserID, } return result diff --git a/api/get_user_info_test.go b/api/get_user_info_test.go index 833599a6..f4113027 100644 --- a/api/get_user_info_test.go +++ b/api/get_user_info_test.go @@ -13,6 +13,9 @@ func TestGetUserInfo(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + path0 := &GetUserInfoPath{UserID: "SYSOP"} type args struct { diff --git a/api/get_user_visit_count.go b/api/get_user_visit_count.go index 2fa568e1..44bb27f7 100644 --- a/api/get_user_visit_count.go +++ b/api/get_user_visit_count.go @@ -28,6 +28,7 @@ func GetUserVisitCount(remoteAddr string, params interface{}, c *gin.Context) (r if err != nil || statusCode != 200 { return nil, 500, err } + // get pttbbsweb user count currentUserVisitCount := schema.GetUserVisitCount() // total user diff --git a/api/load_article_comments.go b/api/load_article_comments.go index 22fe3a16..711303d2 100644 --- a/api/load_article_comments.go +++ b/api/load_article_comments.go @@ -57,8 +57,8 @@ func LoadArticleComments(remoteAddr string, userID bbs.UUserID, params interface } articleID := thePath.FArticleID.ToArticleID() - // is board-valid-user - _, statusCode, err = isBoardValidUser(boardID, c) + // check permission + err = CheckUserArticlePermReadable(userID, boardID, articleID, true) if err != nil { return nil, statusCode, err } diff --git a/api/load_article_comments_test.go b/api/load_article_comments_test.go index c58bff29..ef9e87f9 100644 --- a/api/load_article_comments_test.go +++ b/api/load_article_comments_test.go @@ -15,9 +15,15 @@ func TestLoadArticleComments(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) + articleSummaries_b := []*bbs.ArticleSummary{testArticleSummary1_b, testArticleSummary2_b} + _, _, _ = deserializeArticlesAndUpdateDB("SYSOP", "10_WhoAmI", articleSummaries_b, 123456892000000000) + articleParams := &GetArticleDetailParams{} articlePath := &GetArticleDetailPath{ FBoardID: apitypes.FBoardID("WhoAmI"), diff --git a/api/load_auto_complete_boards_test.go b/api/load_auto_complete_boards_test.go index 6de308a5..4cff7c79 100644 --- a/api/load_auto_complete_boards_test.go +++ b/api/load_auto_complete_boards_test.go @@ -17,13 +17,13 @@ func TestLoadAutoCompleteBoards(t *testing.T) { setupTest() defer teardownTest() - defer schema.UserReadBoard_c.Drop() + defer schema.UserBoard_c.Drop() - update0 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "1_test1", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - update1 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "2_test2", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update0 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "1_test1", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update1 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "2_test2", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - _, _ = schema.UserReadBoard_c.Update(update0, update0) - _, _ = schema.UserReadBoard_c.Update(update1, update1) + _, _ = schema.UserBoard_c.Update(update0, update0) + _, _ = schema.UserBoard_c.Update(update1, update1) params := NewLoadAutoCompleteBoardsParams() expectedResult := &LoadGeneralBoardsResult{ diff --git a/api/load_bottom_articles.go b/api/load_bottom_articles.go index b90f3004..c67ed1bc 100644 --- a/api/load_bottom_articles.go +++ b/api/load_bottom_articles.go @@ -35,31 +35,57 @@ func LoadBottomArticles(remoteAddr string, userID bbs.UUserID, params interface{ return nil, 500, err } + userBoardPerm, err := CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return nil, 403, err + } + articleSummaries_db, err := schema.GetBottomArticleSummaries(boardID) if err != nil { return nil, 500, err } + articleIDs := make([]bbs.ArticleID, len(articleSummaries_db)) + for idx, each := range articleSummaries_db { + articleIDs[idx] = each.ArticleID + } + + articlePermEditableMap, articlePermDeletableMap, err := CheckUserArticlesPermEditableDeletable(userID, boardID, articleIDs, userBoardPerm) + if err != nil { + return nil, 500, err + } + userReadArticleMap := make(map[bbs.ArticleID]bool) userReadArticleMap, err = checkReadArticles(userID, boardID, userReadArticleMap, articleSummaries_db) if err != nil { return nil, 500, err } - r := NewLoadBottomArticlesResult(articleSummaries_db, userReadArticleMap, userID) + r := NewLoadBottomArticlesResult(articleSummaries_db, userReadArticleMap, articlePermEditableMap, articlePermDeletableMap, userID) return r, 200, nil } -func NewLoadBottomArticlesResult(a_db []*schema.ArticleSummary, userReadArticleMap map[bbs.ArticleID]bool, userID bbs.UUserID) *LoadBottomArticlesResult { +func NewLoadBottomArticlesResult(a_db []*schema.ArticleSummary, userReadArticleMap map[bbs.ArticleID]bool, articlePermEditableMap map[bbs.ArticleID]error, articlePermDeletableMap map[bbs.ArticleID]error, userID bbs.UUserID) *LoadBottomArticlesResult { theList := make([]*apitypes.ArticleSummary, len(a_db)) for i, each_db := range a_db { theList[i] = apitypes.NewArticleSummary(each_db, "") articleID := each_db.ArticleID + isRead, ok := userReadArticleMap[articleID] if ok && isRead { theList[i].Read = true } + + err, ok := articlePermEditableMap[articleID] + if ok && err == nil { + theList[i].Editable = true + } + + err, ok = articlePermDeletableMap[articleID] + if ok && err == nil { + theList[i].Deletable = true + } } return &LoadBottomArticlesResult{List: theList, TokenUser: userID} diff --git a/api/load_bottom_articles_test.go b/api/load_bottom_articles_test.go index 43f1f18f..30f148a9 100644 --- a/api/load_bottom_articles_test.go +++ b/api/load_bottom_articles_test.go @@ -19,14 +19,17 @@ func TestLoadBottomArticles(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) - update0 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", UpdateNanoTS: types.Time8(1534567891).ToNanoTS()} - update1 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VrooM21", UpdateNanoTS: types.Time8(1234567800).ToNanoTS()} + update0 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", ReadUpdateNanoTS: types.Time8(1534567891).ToNanoTS()} + update1 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VrooM21", ReadUpdateNanoTS: types.Time8(1234567800).ToNanoTS()} - _, _ = schema.UserReadArticle_c.Update(update0, update0) - _, _ = schema.UserReadArticle_c.Update(update1, update1) + _, _ = schema.UserArticle_c.Update(update0, update0) + _, _ = schema.UserArticle_c.Update(update1, update1) // load articles ctx := context.Background() @@ -66,6 +69,9 @@ func TestLoadBottomArticles(t *testing.T) { Read: false, Idx: "1234560000@19bUG021", + + Editable: true, + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -82,7 +88,8 @@ func TestLoadBottomArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1234567890.A.123", Read: true, - Idx: "1234567890@19bWBI4Z", + Idx: "1234567890@19bWBI4Z", + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -99,7 +106,8 @@ func TestLoadBottomArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1607937174.A.081", Read: false, - Idx: "1607937174@1VrooM21", + Idx: "1607937174@1VrooM21", + Deletable: true, }, }, NextIdx: "", diff --git a/api/load_class_boards.go b/api/load_class_boards.go index 87119dac..85d59850 100644 --- a/api/load_class_boards.go +++ b/api/load_class_boards.go @@ -59,7 +59,7 @@ func LoadClassBoards(remoteAddr string, userID bbs.UUserID, params interface{}, } // is board-valid-user - _, statusCode, err = isBoardValidUser(boardID, c) + _, err = CheckUserBoardPermReadable(userID, boardID) if err != nil { return nil, statusCode, err } diff --git a/api/load_class_boards_test.go b/api/load_class_boards_test.go index 3603d2b0..60ba12f7 100644 --- a/api/load_class_boards_test.go +++ b/api/load_class_boards_test.go @@ -18,6 +18,9 @@ func TestLoadClassBoards(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + ret := mockhttp.LoadGeneralBoards(nil) updateNanoTS := types.NowNanoTS() @@ -26,11 +29,11 @@ func TestLoadClassBoards(t *testing.T) { boardSummaries0[idx] = schema.NewBoardSummary(each_b, updateNanoTS) } - update0 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "1_test1", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - update1 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "2_test2", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update0 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "1_test1", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update1 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "2_test2", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - _, _ = schema.UserReadBoard_c.Update(update0, update0) - _, _ = schema.UserReadBoard_c.Update(update1, update1) + _, _ = schema.UserBoard_c.Update(update0, update0) + _, _ = schema.UserBoard_c.Update(update1, update1) _ = schema.UpdateBoardSummaries(boardSummaries0, updateNanoTS) diff --git a/api/load_general_articles.go b/api/load_general_articles.go index 1f220008..9ef4fbed 100644 --- a/api/load_general_articles.go +++ b/api/load_general_articles.go @@ -61,12 +61,29 @@ func LoadGeneralArticles(remoteAddr string, userID bbs.UUserID, params interface return nil, 500, err } + // check board permission + userBoardPerm, err := CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return nil, 403, err + } + // backend load-general-articles articleSummaries_db, err := schema.GetArticleSummaries(boardID, theParams.StartIdx, theParams.Descending, theParams.Max+1) if err != nil { return nil, 500, err } + articleIDs := make([]bbs.ArticleID, len(articleSummaries_db)) + for idx, each := range articleSummaries_db { + articleIDs[idx] = each.ArticleID + } + + // check article permission + articlePermEditableMap, articlePermDeletableMap, err := CheckUserArticlesPermEditableDeletable(userID, boardID, articleIDs, userBoardPerm) + if err != nil { + return nil, 500, err + } + nextIdx := "" if len(articleSummaries_db) == theParams.Max+1 { nextArticleSummary := articleSummaries_db[theParams.Max] @@ -81,7 +98,7 @@ func LoadGeneralArticles(remoteAddr string, userID bbs.UUserID, params interface return nil, 500, err } - r := NewLoadGeneralArticlesResult(articleSummaries_db, userReadArticleMap, nextIdx, userID) + r := NewLoadGeneralArticlesResult(articleSummaries_db, userReadArticleMap, articlePermEditableMap, articlePermDeletableMap, nextIdx, userID) // update user_read_board if is-newest if theParams.Descending && theParams.StartIdx == "" || !theParams.Descending && nextIdx == "" { @@ -120,7 +137,7 @@ func checkReadArticles(userID bbs.UUserID, boardID bbs.BBoardID, userReadArticle // the Read flag is set based on the existing db.UpdateNanoTS for _, each := range dbResults { eachArticleID := each.ArticleID - eachReadNanoTS := each.UpdateNanoTS + eachReadNanoTS := each.ReadUpdateNanoTS listIdx, ok := checkArticleIDMap[eachArticleID] if !ok { @@ -137,7 +154,7 @@ func checkReadArticles(userID bbs.UUserID, boardID bbs.BBoardID, userReadArticle } func updateUserReadBoard(userID bbs.UUserID, boardID bbs.BBoardID, updateNanoTS types.NanoTS) (err error) { - userReadBoard := &schema.UserReadBoard{UserID: userID, BBoardID: boardID, UpdateNanoTS: updateNanoTS} + userReadBoard := &schema.UserReadBoard{UserID: userID, BBoardID: boardID, ReadUpdateNanoTS: updateNanoTS} err = schema.UpdateUserReadBoard(userReadBoard) if err != nil { @@ -147,15 +164,26 @@ func updateUserReadBoard(userID bbs.UUserID, boardID bbs.BBoardID, updateNanoTS return nil } -func NewLoadGeneralArticlesResult(a_db []*schema.ArticleSummary, userReadArticleMap map[bbs.ArticleID]bool, nextIdx string, userID bbs.UUserID) *LoadGeneralArticlesResult { +func NewLoadGeneralArticlesResult(a_db []*schema.ArticleSummary, userReadArticleMap map[bbs.ArticleID]bool, articlePermEditableMap map[bbs.ArticleID]error, articlePermDeletableMap map[bbs.ArticleID]error, nextIdx string, userID bbs.UUserID) *LoadGeneralArticlesResult { theList := make([]*apitypes.ArticleSummary, len(a_db)) for i, each_db := range a_db { - theList[i] = apitypes.NewArticleSummary(each_db, "") + theList[i] = apitypes.NewArticleSummary(each_db, userID) articleID := each_db.ArticleID + isRead, ok := userReadArticleMap[articleID] if ok && isRead { theList[i].Read = true } + + err, ok := articlePermEditableMap[articleID] + if ok && err == nil { + theList[i].Editable = true + } + + err, ok = articlePermDeletableMap[articleID] + if ok && err == nil { + theList[i].Deletable = true + } } return &LoadGeneralArticlesResult{ diff --git a/api/load_general_articles_by_keyword.go b/api/load_general_articles_by_keyword.go index 8e5aabc5..e164b5da 100644 --- a/api/load_general_articles_by_keyword.go +++ b/api/load_general_articles_by_keyword.go @@ -3,7 +3,6 @@ package api import ( "github.com/Ptt-official-app/go-pttbbs/bbs" ptttypes "github.com/Ptt-official-app/go-pttbbs/types" - "github.com/Ptt-official-app/go-pttbbsweb/apitypes" "github.com/Ptt-official-app/go-pttbbsweb/schema" "github.com/Ptt-official-app/go-pttbbsweb/types" "github.com/gin-gonic/gin" @@ -25,10 +24,10 @@ func LoadGeneralArticlesByKeyword(remoteAddr string, userID bbs.UUserID, params return nil, 500, err } - // is board-valid-user - _, statusCode, err = isBoardValidUser(boardID, c) + // check board permission + userBoardPerm, err := CheckUserBoardPermReadable(userID, boardID) if err != nil { - return nil, statusCode, err + return nil, 403, err } // get article-summaries @@ -50,59 +49,29 @@ func LoadGeneralArticlesByKeyword(remoteAddr string, userID bbs.UUserID, params return nil, 500, err } - // nextIdx - var nextIdx string - if len(articleSummaries_db) > theParams.Max { - nextIdx = articleSummaries_db[theParams.Max].Idx - articleSummaries_db = articleSummaries_db[:theParams.Max] + articleIDs := make([]bbs.ArticleID, len(articleSummaries_db)) + for idx, each := range articleSummaries_db { + articleIDs[idx] = each.ArticleID } - userReadArticleMap, err := getUserReadArticleMap(userID, boardID, articleSummaries_db) + // check article permission + articlePermEditableMap, articlePermDeletableMap, err := CheckUserArticlesPermEditableDeletable(userID, boardID, articleIDs, userBoardPerm) if err != nil { return nil, 500, err } - return NewLoadGeneralArticlesResultByKeyword(articleSummaries_db, userReadArticleMap, nextIdx, userID), 200, nil -} - -func getUserReadArticleMap(userID bbs.UUserID, boardID bbs.BBoardID, theList []*schema.ArticleSummary) (userReadArticleMap map[bbs.ArticleID]types.NanoTS, err error) { - queryArticleIDs := make([]bbs.ArticleID, len(theList)) - for idx, each := range theList { - queryArticleIDs[idx] = each.ArticleID + // nextIdx + var nextIdx string + if len(articleSummaries_db) > theParams.Max { + nextIdx = articleSummaries_db[theParams.Max].Idx + articleSummaries_db = articleSummaries_db[:theParams.Max] } - dbResults, err := schema.FindUserReadArticles(userID, boardID, queryArticleIDs) + userReadArticleMap := make(map[bbs.ArticleID]bool) + userReadArticleMap, err = checkReadArticles(userID, boardID, userReadArticleMap, articleSummaries_db) if err != nil { - return nil, err - } - - userReadArticleMap = make(map[bbs.ArticleID]types.NanoTS) - for _, each := range dbResults { - userReadArticleMap[each.ArticleID] = each.UpdateNanoTS - } - - return userReadArticleMap, nil -} - -func NewLoadGeneralArticlesResultByKeyword(a_db []*schema.ArticleSummary, userReadArticleMap map[bbs.ArticleID]types.NanoTS, nextIdx string, userID bbs.UUserID) (result *LoadGeneralArticlesResult) { - theList := make([]*apitypes.ArticleSummary, len(a_db)) - for i, each_db := range a_db { - theList[i] = apitypes.NewArticleSummary(each_db, "") - readNanoTS, ok := userReadArticleMap[each_db.ArticleID] - if !ok { - continue - } - if readNanoTS > each_db.MTime { - theList[i].Read = true - } else if readNanoTS > each_db.CreateTime { - theList[i].Read = true - } + return nil, 500, err } - return &LoadGeneralArticlesResult{ - List: theList, - NextIdx: nextIdx, - - TokenUser: userID, - } + return NewLoadGeneralArticlesResult(articleSummaries_db, userReadArticleMap, articlePermEditableMap, articlePermDeletableMap, nextIdx, userID), 200, nil } diff --git a/api/load_general_articles_test.go b/api/load_general_articles_test.go index f32479bc..dfe0184e 100644 --- a/api/load_general_articles_test.go +++ b/api/load_general_articles_test.go @@ -2,8 +2,6 @@ package api import ( "context" - "net/http" - "net/url" "sync" "testing" @@ -21,14 +19,17 @@ func TestLoadGeneralArticles(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) - update0 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VtWRel9", UpdateNanoTS: types.Time8(1608386300).ToNanoTS()} - update1 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", UpdateNanoTS: types.Time8(1234567990).ToNanoTS()} + update0 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VtWRel9", ReadUpdateNanoTS: types.Time8(1608386300).ToNanoTS()} + update1 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", ReadUpdateNanoTS: types.Time8(1234567990).ToNanoTS()} - _, _ = schema.UserReadArticle_c.Update(update0, update0) - _, _ = schema.UserReadArticle_c.Update(update1, update1) + _, _ = schema.UserArticle_c.Update(update0, update0) + _, _ = schema.UserArticle_c.Update(update1, update1) // load articles ctx := context.Background() @@ -68,6 +69,10 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1608388506.A.85D", Read: false, Idx: "1608388506@1VtW-QXT", + + TokenUser: "SYSOP", + Editable: true, + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -84,6 +89,10 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1608386280.A.BC9", Read: true, Idx: "1608386280@1VtWRel9", + + TokenUser: "SYSOP", + Editable: true, + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -100,6 +109,10 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1607937174.A.081", Read: false, Idx: "1607937174@1VrooM21", + + TokenUser: "SYSOP", + Editable: false, + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -116,6 +129,10 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1607202240.A.30D", Read: false, Idx: "1607202240@1Vo_N0CD", + + TokenUser: "SYSOP", + Editable: false, + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -132,6 +149,10 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1584665022.A.ED0", Read: false, Idx: "1584665022@1UT16-xG", + + TokenUser: "SYSOP", + Editable: false, + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -148,6 +169,10 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1234567890.A.123", Read: true, Idx: "1234567890@19bWBI4Z", + + TokenUser: "SYSOP", + Editable: false, + Deletable: true, }, { FBoardID: apitypes.FBoardID("WhoAmI"), @@ -164,6 +189,10 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1234560000.A.081", Read: false, Idx: "1234560000@19bUG021", + + TokenUser: "SYSOP", + Editable: true, + Deletable: true, }, }, NextIdx: "", @@ -193,14 +222,15 @@ func TestLoadGeneralArticles(t *testing.T) { URL: "http://localhost:3457/bbs/board/WhoAmI/article/M.1234567890.A.123", Read: true, Idx: "1234567890@19bWBI4Z", + + TokenUser: "SYSOP", + Deletable: true, }, }, TokenUser: "SYSOP", } - c := &gin.Context{} - c.Request = &http.Request{URL: &url.URL{Path: "/api/board/test1/articles"}} type args struct { remoteAddr string userID bbs.UUserID diff --git a/api/load_general_boards_by_class_test.go b/api/load_general_boards_by_class_test.go index 29acece9..9ee32de1 100644 --- a/api/load_general_boards_by_class_test.go +++ b/api/load_general_boards_by_class_test.go @@ -17,13 +17,13 @@ func TestLoadGeneralBoardsByClass(t *testing.T) { setupTest() defer teardownTest() - defer schema.UserReadBoard_c.Drop() + defer schema.UserBoard_c.Drop() - update0 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "1_test1", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - update1 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "2_test2", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update0 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "1_test1", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update1 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "2_test2", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - _, _ = schema.UserReadBoard_c.Update(update0, update0) - _, _ = schema.UserReadBoard_c.Update(update1, update1) + _, _ = schema.UserBoard_c.Update(update0, update0) + _, _ = schema.UserBoard_c.Update(update1, update1) params := &LoadGeneralBoardsParams{} expectedResult := &LoadGeneralBoardsResult{ diff --git a/api/load_general_boards_test.go b/api/load_general_boards_test.go index 4db31165..5a3d55dd 100644 --- a/api/load_general_boards_test.go +++ b/api/load_general_boards_test.go @@ -17,11 +17,11 @@ func TestLoadGeneralBoards(t *testing.T) { setupTest() defer teardownTest() - update0 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "1_test1", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - update1 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "2_test2", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update0 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "1_test1", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update1 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "2_test2", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - _, _ = schema.UserReadBoard_c.Update(update0, update0) - _, _ = schema.UserReadBoard_c.Update(update1, update1) + _, _ = schema.UserBoard_c.Update(update0, update0) + _, _ = schema.UserBoard_c.Update(update1, update1) params := NewLoadGeneralBoardsParams() expectedResult := &LoadGeneralBoardsResult{ diff --git a/api/load_popular_boards_test.go b/api/load_popular_boards_test.go index 0781591f..c8836fdb 100644 --- a/api/load_popular_boards_test.go +++ b/api/load_popular_boards_test.go @@ -17,13 +17,13 @@ func TestLoadPopularBoards(t *testing.T) { setupTest() defer teardownTest() - defer schema.UserReadBoard_c.Drop() + defer schema.UserBoard_c.Drop() - update0 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "1_test1", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - update1 := &schema.UserReadBoard{UserID: "SYSOP", BBoardID: "2_test2", UpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update0 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "1_test1", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} + update1 := &schema.UserBoard{UserID: "SYSOP", BBoardID: "2_test2", ReadUpdateNanoTS: types.Time8(1234567891).ToNanoTS()} - _, _ = schema.UserReadBoard_c.Update(update0, update0) - _, _ = schema.UserReadBoard_c.Update(update1, update1) + _, _ = schema.UserBoard_c.Update(update0, update0) + _, _ = schema.UserBoard_c.Update(update1, update1) expectedResult := &LoadPopularBoardsResult{ List: []*apitypes.BoardSummary{ diff --git a/api/load_user_articles.go b/api/load_user_articles.go index 3d7de059..a4e46f87 100644 --- a/api/load_user_articles.go +++ b/api/load_user_articles.go @@ -57,7 +57,7 @@ func LoadUserArticles(remoteAddr string, userID bbs.UUserID, params interface{}, return nil, 500, err } - userReadArticleMap, err := checkReadUserArticles(userID, articleSummaries_db) + userReadArticleMap, err := checkReadUserBoardArticles(userID, articleSummaries_db) if err != nil { return nil, 500, err } @@ -132,15 +132,16 @@ func isValidArticleSummaries(articleSummaries_db []*schema.ArticleSummary) ([]*s return articleSummaries_db, nil } -func checkReadUserArticles(userID bbs.UUserID, theList []*schema.ArticleSummary) (userReadArticleMap map[bbs.ArticleID]bool, err error) { - queryArticleIDs := make([]bbs.ArticleID, 0, len(theList)) - checkArticleIDMap := make(map[bbs.ArticleID]int) +func checkReadUserBoardArticles(userID bbs.UUserID, theList []*schema.ArticleSummary) (userReadBoardArticleMap map[types.BoardArticleID]bool, err error) { + queryBoardArticleIDs := make([]types.BoardArticleID, 0, len(theList)) + checkBoardArticleIDMap := make(map[types.BoardArticleID]int) for idx, each := range theList { - checkArticleIDMap[each.ArticleID] = idx - queryArticleIDs = append(queryArticleIDs, each.ArticleID) + eachBoardArticleID := types.ToBoardArticleID(each.BBoardID, each.ArticleID) + checkBoardArticleIDMap[eachBoardArticleID] = idx + queryBoardArticleIDs = append(queryBoardArticleIDs, eachBoardArticleID) } - dbResults, err := schema.FindUserReadArticlesByArticleIDs(userID, queryArticleIDs) + dbResults, err := schema.FindUserReadArticlesByBoardArticleIDs(userID, queryBoardArticleIDs) if err != nil { return nil, err } @@ -148,12 +149,12 @@ func checkReadUserArticles(userID bbs.UUserID, theList []*schema.ArticleSummary) // setup read in the list // no need to update db, because we don't read the article yet. // the Read flag is set based on the existing db.UpdateNanoTS - userReadArticleMap = make(map[bbs.ArticleID]bool) + userReadBoardArticleMap = make(map[types.BoardArticleID]bool) for _, each := range dbResults { - eachArticleID := each.ArticleID - eachReadNanoTS := each.UpdateNanoTS + eachBoardArticleID := each.BoardArticleID + eachReadNanoTS := each.ReadUpdateNanoTS - listIdx, ok := checkArticleIDMap[eachArticleID] + listIdx, ok := checkBoardArticleIDMap[eachBoardArticleID] if !ok { continue } @@ -161,18 +162,18 @@ func checkReadUserArticles(userID bbs.UUserID, theList []*schema.ArticleSummary) eachInTheList := theList[listIdx] eachPostNanoTS := eachInTheList.CreateTime isRead := eachReadNanoTS > eachPostNanoTS - userReadArticleMap[eachArticleID] = isRead + userReadBoardArticleMap[eachBoardArticleID] = isRead } - return userReadArticleMap, nil + return userReadBoardArticleMap, nil } -func NewLoadUserArticlesResult(a_db []*schema.ArticleSummary, userReadArticleMap map[bbs.ArticleID]bool, nextIdx string, userID bbs.UUserID) *LoadUserArticlesResult { +func NewLoadUserArticlesResult(a_db []*schema.ArticleSummary, userReadBoardArticleMap map[types.BoardArticleID]bool, nextIdx string, userID bbs.UUserID) *LoadUserArticlesResult { theList := make([]*apitypes.ArticleSummary, len(a_db)) for i, each_db := range a_db { theList[i] = apitypes.NewArticleSummary(each_db, "") - articleID := each_db.ArticleID - isRead, ok := userReadArticleMap[articleID] + boardArticleID := types.ToBoardArticleID(each_db.BBoardID, each_db.ArticleID) + isRead, ok := userReadBoardArticleMap[boardArticleID] if ok && isRead { theList[i].Read = true } diff --git a/api/load_user_articles_test.go b/api/load_user_articles_test.go index a82f6404..f844d956 100644 --- a/api/load_user_articles_test.go +++ b/api/load_user_articles_test.go @@ -22,11 +22,11 @@ func TestLoadUserArticles(t *testing.T) { boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b, testBoardSummarySYSOP_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) - update0 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", UpdateNanoTS: types.Time8(1534567891).ToNanoTS()} - update1 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VrooM21", UpdateNanoTS: types.Time8(1234567800).ToNanoTS()} + update0 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", BoardArticleID: "10_WhoAmI:19bWBI4Z", ReadUpdateNanoTS: types.Time8(1534567891).ToNanoTS()} + update1 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VrooM21", BoardArticleID: "10_WhoAmI:1VrooM21", ReadUpdateNanoTS: types.Time8(1234567800).ToNanoTS()} - _, _ = schema.UserReadArticle_c.Update(update0, update0) - _, _ = schema.UserReadArticle_c.Update(update1, update1) + _, _ = schema.UserArticle_c.Update(update0, update0) + _, _ = schema.UserArticle_c.Update(update1, update1) // load articles ctx := context.Background() diff --git a/api/load_user_comments.go b/api/load_user_comments.go index 656b87b6..726ba53b 100644 --- a/api/load_user_comments.go +++ b/api/load_user_comments.go @@ -56,12 +56,12 @@ func LoadUserComments(remoteAddr string, userID bbs.UUserID, params interface{}, return nil, 500, err } - articleSummaryMap, userReadArticleMap, err := getArticleMapFromCommentSummaries(userID, commentSummaries_db) + articleSummaryMap, userReadBoardArticleMap, err := getArticleSummaryMapFromCommentSummaries(userID, commentSummaries_db) if err != nil { return nil, 500, err } - r := NewLoadUserCommentsResult(commentSummaries_db, articleSummaryMap, userReadArticleMap, nextIdx, userID) + r := NewLoadUserCommentsResult(commentSummaries_db, articleSummaryMap, userReadBoardArticleMap, nextIdx, userID) return r, 200, nil } @@ -130,74 +130,82 @@ func isValidCommentSummaries(commentSummaries_db []*schema.CommentSummary) ([]*s return commentSummaries_db, nil } -func getArticleMapFromCommentSummaries(userID bbs.UUserID, commentSummaries_db []*schema.CommentSummary) (articleSummaryMap map[bbs.ArticleID]*schema.ArticleSummary, userReadArticleMap map[bbs.ArticleID]types.NanoTS, err error) { - articleIDs := make([]bbs.ArticleID, 0, len(commentSummaries_db)) - articleIDMap := make(map[bbs.ArticleID]bool) +func getArticleSummaryMapFromCommentSummaries(userID bbs.UUserID, commentSummaries_db []*schema.CommentSummary) (articleSummaryMap map[types.BoardArticleID]*schema.ArticleSummary, userReadBoardArticleMap map[types.BoardArticleID]types.NanoTS, err error) { + boardArticleIDs := make([]types.BoardArticleID, 0, len(commentSummaries_db)) + boardArticleIDMap := make(map[types.BoardArticleID]bool) + + var eachBoardArticleID types.BoardArticleID for _, each := range commentSummaries_db { - _, ok := articleIDMap[each.ArticleID] + eachBoardArticleID = types.ToBoardArticleID(each.BBoardID, each.ArticleID) + _, ok := boardArticleIDMap[eachBoardArticleID] if ok { continue } - articleIDMap[each.ArticleID] = true - articleIDs = append(articleIDs, each.ArticleID) + boardArticleIDMap[eachBoardArticleID] = true + boardArticleIDs = append(boardArticleIDs, eachBoardArticleID) } // article summaries - articleSummaries, err := schema.GetArticleSummariesByArticleIDs(articleIDs) + articleSummaries, err := schema.GetArticleSummariesByBoardArticleIDs(boardArticleIDs) if err != nil { return nil, nil, err } - articleSummaryMap = make(map[bbs.ArticleID]*schema.ArticleSummary) + articleSummaryMap = make(map[types.BoardArticleID]*schema.ArticleSummary) for _, each := range articleSummaries { - articleSummaryMap[each.ArticleID] = each + articleSummaryMap[each.BoardArticleID] = each } // user read articles - userReadArticles, err := schema.FindUserReadArticlesByArticleIDs(userID, articleIDs) + userReadArticles, err := schema.FindUserReadArticlesByBoardArticleIDs(userID, boardArticleIDs) if err != nil { return nil, nil, err } - userReadArticleMap = make(map[bbs.ArticleID]types.NanoTS) + userReadBoardArticleMap = make(map[types.BoardArticleID]types.NanoTS) for _, each := range userReadArticles { - userReadArticleMap[each.ArticleID] = each.UpdateNanoTS + userReadBoardArticleMap[each.BoardArticleID] = each.ReadUpdateNanoTS } - return articleSummaryMap, userReadArticleMap, nil + return articleSummaryMap, userReadBoardArticleMap, nil } func NewLoadUserCommentsResult( commentSummaries_db []*schema.CommentSummary, - articleSummaryMap map[bbs.ArticleID]*schema.ArticleSummary, - userReadArticleMap map[bbs.ArticleID]types.NanoTS, + articleSummaryMap map[types.BoardArticleID]*schema.ArticleSummary, + userReadBoardArticleMap map[types.BoardArticleID]types.NanoTS, nextIdx string, userID bbs.UUserID, ) (result *LoadUserCommentsResult) { - comments := make([]*apitypes.ArticleComment, len(commentSummaries_db)) - for idx, each := range commentSummaries_db { - articleSummary, ok := articleSummaryMap[each.ArticleID] + comments := make([]*apitypes.ArticleComment, 0, len(commentSummaries_db)) + for _, each := range commentSummaries_db { + boardArticleID := types.ToBoardArticleID(each.BBoardID, each.ArticleID) + articleSummary, ok := articleSummaryMap[boardArticleID] if !ok { continue } - comments[idx] = apitypes.NewArticleCommentFromComment(articleSummary, each) + + eachComment := apitypes.NewArticleCommentFromComment(articleSummary, each) // read - readNanoTS, ok := userReadArticleMap[each.ArticleID] + readNanoTS, ok := userReadBoardArticleMap[boardArticleID] if !ok { + comments = append(comments, eachComment) continue } if readNanoTS > articleSummary.MTime { - comments[idx].Read = types.READ_STATUS_MTIME + eachComment.Read = types.READ_STATUS_MTIME } else if readNanoTS > each.SortTime { - comments[idx].Read = types.READ_STATUS_COMMENT_TIME + eachComment.Read = types.READ_STATUS_COMMENT_TIME } else if readNanoTS > each.CreateTime { - comments[idx].Read = types.READ_STATUS_CREATE_TIME + eachComment.Read = types.READ_STATUS_CREATE_TIME } else { - comments[idx].Read = types.READ_STATUS_UNREAD + eachComment.Read = types.READ_STATUS_UNREAD } + + comments = append(comments, eachComment) } nextIdx = apitypes.SerializeArticleCommentIdx(apitypes.ARTICLE_COMMENT_TYPE_COMMENT, nextIdx) diff --git a/api/load_user_comments_test.go b/api/load_user_comments_test.go index 64eba399..e42de451 100644 --- a/api/load_user_comments_test.go +++ b/api/load_user_comments_test.go @@ -17,16 +17,22 @@ func TestLoadUserComments(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + // board boardSummaries_b := []*bbs.BoardSummary{testBoardSummaryWhoAmI_b, testBoardSummarySYSOP_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) + articleSummaries_b := []*bbs.ArticleSummary{testArticleSummary1_b, testArticleSummary2_b} + _, _, _ = deserializeArticlesAndUpdateDB("SYSOP", "10_WhoAmI", articleSummaries_b, 123456892000000000) + // articles - update0 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", UpdateNanoTS: types.Time8(1534567891).ToNanoTS()} - update1 := &schema.UserReadArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VrooM21", UpdateNanoTS: types.Time8(1234567800).ToNanoTS()} + update0 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "19bWBI4Z", BoardArticleID: "10_WhoAmI:19bWBI4Z", ReadUpdateNanoTS: types.Time8(1608388624).ToNanoTS()} + update1 := &schema.UserArticle{UserID: "SYSOP", BoardID: "10_WhoAmI", ArticleID: "1VrooM21", BoardArticleID: "10_WhoAmI:1VrooM21", ReadUpdateNanoTS: types.Time8(1608388624).ToNanoTS()} - _, _ = schema.UserReadArticle_c.Update(update0, update0) - _, _ = schema.UserReadArticle_c.Update(update1, update1) + _, _ = schema.UserArticle_c.Update(update0, update0) + _, _ = schema.UserArticle_c.Update(update1, update1) paramsLoadGeneralArticles := NewLoadGeneralArticlesParams() pathLoadGeneralArticles := &LoadGeneralArticlesPath{FBoardID: "WhoAmI"} @@ -58,7 +64,7 @@ func TestLoadUserComments(t *testing.T) { MTime: 1607937100, Recommend: 3, NComments: 3, - Owner: "SYSOP", + Owner: "teemo", Title: "再來呢?~", Money: 12, Class: "問題", @@ -88,7 +94,7 @@ func TestLoadUserComments(t *testing.T) { MTime: 1607937100, Recommend: 3, NComments: 3, - Owner: "SYSOP", + Owner: "teemo", Title: "再來呢?~", Money: 12, Class: "問題", diff --git a/api/reply_comments.go b/api/reply_comments.go index 7510adb9..0178c966 100644 --- a/api/reply_comments.go +++ b/api/reply_comments.go @@ -48,6 +48,12 @@ func ReplyComments(remoteAddr string, userID bbs.UUserID, params interface{}, pa } articleID := thePath.FArticleID.ToArticleID() + // check permission + err = CheckUserArticlePermReadable(userID, boardID, articleID, true) + if err != nil { + return nil, 403, err + } + oldContent, oldContentPrefix, oldSignatureDBCS, articleDetailSummary_db, oldSZ, oldsum, statusCode, err := editArticleGetArticleContentInfo(userID, boardID, articleID, c, true) if err != nil { return nil, statusCode, err diff --git a/api/reply_comments_test.go b/api/reply_comments_test.go index 9739edde..3d4c91fb 100644 --- a/api/reply_comments_test.go +++ b/api/reply_comments_test.go @@ -17,9 +17,15 @@ func TestReplyComments(t *testing.T) { setupTest() defer teardownTest() + _, _ = deserializeUserDetailAndUpdateDB(testUserSYSOP_b, 123456890000000000) + _, _ = deserializeUserDetailAndUpdateDB(testUserChhsiao123_b, 123456891000000000) + boardSummaries_b := []*bbs.BoardSummary{testBoardSummarySYSOP_b} _, _, _ = deserializeBoardsAndUpdateDB("SYSOP", boardSummaries_b, 123456890000000000) + articleSummaries_b := []*bbs.ArticleSummary{testArticleSummary1_b, testArticleSummary2_b} + _, _, _ = deserializeArticlesAndUpdateDB("SYSOP", "10_WhoAmI", articleSummaries_b, 123456892000000000) + createArticlePath0 := &CreateArticlePath{ FBoardID: "SYSOP", } diff --git a/api/testcases_test.go b/api/testcases_test.go index 080ee958..15825ae2 100644 --- a/api/testcases_test.go +++ b/api/testcases_test.go @@ -1,12 +1,12 @@ package api import ( - "io" - "io/ioutil" "os" + pttbbsapi "github.com/Ptt-official-app/go-pttbbs/api" "github.com/Ptt-official-app/go-pttbbs/bbs" "github.com/Ptt-official-app/go-pttbbs/ptttype" + "github.com/Ptt-official-app/go-pttbbs/types" "github.com/Ptt-official-app/go-pttbbsweb/apitypes" ) @@ -192,6 +192,50 @@ var ( testDeleteBoardSummary8, } + testUserSYSOP_b = pttbbsapi.GetUserResult(&bbs.Userec{ + Version: 4194, + UUserID: bbs.UUserID("SYSOP"), + Username: "SYSOP", + Realname: []byte{ // CodingMan + 0x43, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x4d, 0x61, 0x6e, + }, + Nickname: []byte{0xaf, 0xab}, // 神 + + Uflag: ptttype.UF_CURSOR_ASCII | ptttype.UF_DBCS_DROP_REPEAT | ptttype.UF_DBCS_AWARE | ptttype.UF_ADBANNER | ptttype.UF_BRDSORT, + Userlevel: ptttype.PERM_SYSSUBOP | ptttype.PERM_BM | ptttype.PERM_BASIC | ptttype.PERM_CHAT | ptttype.PERM_PAGE | ptttype.PERM_POST | ptttype.PERM_LOGINOK | ptttype.PERM_SYSOP, + Numlogindays: 2, + Numposts: 0, + Firstlogin: 1600681288, + Lastlogin: 1600756094, + Lasthost: "59.124.167.226", + /* + Address: []byte{ //新竹縣子虛鄉烏有村543號 + 0xb7, 0x73, 0xa6, 0xcb, 0xbf, 0xa4, 0xa4, 0x6c, 0xb5, 0xea, + 0xb6, 0x6d, 0xaf, 0x51, 0xa6, 0xb3, 0xa7, 0xf8, 0x35, 0x34, + 0x33, 0xb8, 0xb9, + }, + */ + Over18: true, + Pager: ptttype.PAGER_ON, + Career: []byte{0xa5, 0xfe, 0xb4, 0xba, 0xb3, 0x6e, 0xc5, 0xe9}, // 全景軟體 + LastSeen: 1600681288, + }) + + testUserChhsiao123_b = pttbbsapi.GetUserResult(&bbs.Userec{ + Version: ptttype.PASSWD_VERSION, + UUserID: bbs.UUserID("chhsiao123"), + Username: "chhsiao123", + Lasthost: "127.0.0.1", + Uflag: ptttype.UF_CURSOR_ASCII | ptttype.UF_DBCS_DROP_REPEAT | ptttype.UF_DBCS_AWARE | ptttype.UF_ADBANNER | ptttype.UF_BRDSORT, + Userlevel: ptttype.PERM_BASIC | ptttype.PERM_CHAT | ptttype.PERM_PAGE | ptttype.PERM_POST | ptttype.PERM_LOGINOK, + Firstlogin: 1600681290, + Lastlogin: 1600681290, + Numlogindays: 1, + Pager: ptttype.PAGER_ON, + Over18: true, + LastSeen: 1600681290, + }) + testBoardSummaryWhoAmI_b = &bbs.BoardSummary{ Gid: 5, Bid: 10, @@ -229,6 +273,42 @@ var ( IdxByName: "SYSOP", IdxByClass: "vFSt-Q@SYSOP", } + + testArticleSummary1_b = &bbs.ArticleSummary{ + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VrooM21"), + IsDeleted: false, + Filename: "M.1607937174.A.081", + CreateTime: types.Time4(1607937174), + MTime: types.Time4(1607937100), + Recommend: 3, + Owner: bbs.UUserID("teemo"), + Class: []byte{0xb0, 0xdd, 0xc3, 0x44}, + FullTitle: []byte{0x5b, 0xb0, 0xdd, 0xc3, 0x44, 0x5d, 0xa6, 0x41, 0xa8, 0xd3, 0xa9, 0x4f, 0xa1, 0x48, 0xa1, 0xe3}, //[問題]再來呢?~ + Money: 12, + Filemode: 0, + Read: false, + Idx: "1607937174@1VrooM21", + RealTitle: []byte{0xa6, 0x41, 0xa8, 0xd3, 0xa9, 0x4f, 0xa1, 0x48, 0xa1, 0xe3}, + } + + testArticleSummary2_b = &bbs.ArticleSummary{ + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VtWRel9"), + IsDeleted: false, + Filename: "M.1608386280.A.BC9", + CreateTime: types.Time4(1608386280), + MTime: types.Time4(1608386280), + Recommend: 8, + Owner: bbs.UUserID("SYSOP"), + Class: []byte{0xb0, 0xdd, 0xc3, 0x44}, + FullTitle: []byte{0x5b, 0xb0, 0xdd, 0xc3, 0x44, 0x5d, 0xb5, 0x4d, 0xab, 0xe1, 0xa9, 0x4f, 0xa1, 0x48, 0xa1, 0xe3}, //[問題]然後呢?~ + Money: 3, + Filemode: 0, + Read: false, + Idx: "1608386280@1VtWRel9", + RealTitle: []byte{0xb5, 0x4d, 0xab, 0xe1, 0xa9, 0x4f, 0xa1, 0x48, 0xa1, 0xe3}, + } ) func initTest() { @@ -248,25 +328,17 @@ func initTest() { func loadTest(filename string) (contentAll []byte, content []byte, signature []byte, recommend []byte, firstComments []byte, theRestComments []byte) { // content-all fullFilename := "testcase/" + filename - file0, err := os.Open(fullFilename) + contentAll, err := os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file0.Close() - - r := io.Reader(file0) - contentAll, _ = ioutil.ReadAll(r) // content fullFilename = "testcase/" + filename + ".content" - file1, err := os.Open(fullFilename) + content, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file1.Close() - - r = io.Reader(file1) - content, _ = ioutil.ReadAll(r) if len(content) == 0 { content = nil @@ -274,14 +346,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // signature fullFilename = "testcase/" + filename + ".signature" - file2, err := os.Open(fullFilename) + signature, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file2.Close() - - r = io.Reader(file2) - signature, _ = ioutil.ReadAll(r) if len(signature) == 0 { signature = nil @@ -289,14 +357,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // recommend fullFilename = "testcase/" + filename + ".recommend" - file3, err := os.Open(fullFilename) + recommend, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file3.Close() - - r = io.Reader(file3) - recommend, _ = ioutil.ReadAll(r) if len(recommend) == 0 { recommend = nil @@ -304,14 +368,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // firstComments fullFilename = "testcase/" + filename + ".firstComments" - file4, err := os.Open(fullFilename) + firstComments, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file4.Close() - - r = io.Reader(file4) - firstComments, _ = ioutil.ReadAll(r) if len(firstComments) == 0 { firstComments = nil @@ -319,14 +379,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // theRestComments fullFilename = "testcase/" + filename + ".theRestComments" - file5, err := os.Open(fullFilename) + theRestComments, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file5.Close() - - r = io.Reader(file5) - theRestComments, _ = ioutil.ReadAll(r) if len(theRestComments) == 0 { theRestComments = nil diff --git a/api/testinit.go b/api/testinit.go index 1d60084a..c220b759 100644 --- a/api/testinit.go +++ b/api/testinit.go @@ -1,7 +1,7 @@ package api import ( - "io/ioutil" + "os" "github.com/Ptt-official-app/go-pttbbsweb/types" ) @@ -21,7 +21,7 @@ func UnsetIsTest() { types.STATIC_DIR = origStaticDir IsTest = false - data, _ := ioutil.ReadFile("./testcase/home2/t/testUser2/.fav.orig") + data, _ := os.ReadFile("./testcase/home2/t/testUser2/.fav.orig") - _ = ioutil.WriteFile("./testcase/home2/t/testUser2/.fav", data, 0o644) + _ = os.WriteFile("./testcase/home2/t/testUser2/.fav", data, 0o644) } diff --git a/api/user_article_perm.go b/api/user_article_perm.go new file mode 100644 index 00000000..97ba65ec --- /dev/null +++ b/api/user_article_perm.go @@ -0,0 +1,224 @@ +package api + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbsweb/schema" +) + +// CheckUserArticlePermReadable +// +// Readable +func CheckUserArticlePermReadable(userID bbs.UUserID, boardID bbs.BBoardID, articleID bbs.ArticleID, isCheckBoard bool) (err error) { + if isCheckBoard { + _, err = CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return err + } + } + + articlePermInfo, err := schema.GetArticlePermInfo(boardID, articleID) + if err != nil { + return err + } + + if articlePermInfo == nil { + return ErrNoArticle + } + + if articlePermInfo.IsDeleted { + return ErrNoArticle + } + + return nil +} + +// CheckUserArticlePermEditable +// +// Editable +func CheckUserArticlePermEditable(userID bbs.UUserID, boardID bbs.BBoardID, articleID bbs.ArticleID, isCheckBoard bool) (err error) { + if isCheckBoard { + _, err = CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return err + } + } + + articlePermInfo, err := schema.GetArticlePermInfo(boardID, articleID) + if err != nil { + return err + } + if articlePermInfo == nil { + return ErrNoArticle + } + + return checkUserArticlePermEditableCore(userID, boardID, articleID, articlePermInfo) +} + +func checkUserArticlePermEditableCore(userID bbs.UUserID, boardID bbs.BBoardID, articleID bbs.ArticleID, articlePermInfo *schema.ArticlePermInfo) (err error) { + if articlePermInfo.IsDeleted { + return ErrNoArticle + } + + if userID == articlePermInfo.Owner { + return nil + } + + return ErrInvalidUser +} + +// CheckUserArticlesPermEditable +// +// articles Editable +func CheckUserArticlesPermEditable(userID bbs.UUserID, boardID bbs.BBoardID, articleIDs []bbs.ArticleID, userBoardPerm *UserBoardPermReadable) (articlePermMap map[bbs.ArticleID]error, err error) { + if userBoardPerm == nil { + userBoardPerm, err = CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return nil, err + } + } + + if len(articleIDs) == 0 { + return make(map[bbs.ArticleID]error), nil + } + + articlesPermInfo, err := schema.GetArticlesPermInfo(boardID, articleIDs) + if err != nil { + return nil, err + } + if articlesPermInfo == nil { + return nil, ErrNoArticle + } + + return checkUserArticlesPermEditableCore(userID, articlesPermInfo, userBoardPerm), nil +} + +func checkUserArticlesPermEditableCore(userID bbs.UUserID, articlesPermInfo []*schema.ArticlePermInfo, userBoardPerm *UserBoardPermReadable) (articlePermMap map[bbs.ArticleID]error) { + articlePermMap = make(map[bbs.ArticleID]error) + for _, each := range articlesPermInfo { + var err error + if each.IsDeleted { + err = ErrNoArticle + } else if userID == each.Owner { + err = nil + } else { + err = ErrInvalidUser + } + + articlePermMap[each.ArticleID] = err + } + + return articlePermMap +} + +// CheckUserArticlePermDeletable +// +// Deletable +func CheckUserArticlePermDeletable(userID bbs.UUserID, boardID bbs.BBoardID, articleID bbs.ArticleID) (err error) { + userBoardPermReadable, err := CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return err + } + + articlePermInfo, err := schema.GetArticlePermInfo(boardID, articleID) + if err != nil { + return err + } + if articlePermInfo == nil { + return ErrNoArticle + } + + return checkUserArticlePermDeletableCore(userID, boardID, articleID, articlePermInfo, userBoardPermReadable) +} + +func checkUserArticlePermDeletableCore(userID bbs.UUserID, boardID bbs.BBoardID, articleID bbs.ArticleID, articlePermInfo *schema.ArticlePermInfo, userBoardPerm *UserBoardPermReadable) (err error) { + if articlePermInfo.IsDeleted { + return ErrNoArticle + } + + if userID == articlePermInfo.Owner { + return nil + } + + if userBoardPerm.IsBM { + return nil + } + + if userBoardPerm.IsSYSOP { + return nil + } + + return ErrInvalidUser +} + +// CheckUserArticlePermDeletable +// +// Deletable +func CheckUserArticlesPermDeletable(userID bbs.UUserID, boardID bbs.BBoardID, articleIDs []bbs.ArticleID, userBoardPermReadable *UserBoardPermReadable) (articlePermMap map[bbs.ArticleID]error, err error) { + if userBoardPermReadable == nil { + userBoardPermReadable, err = CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return nil, err + } + } + + if len(articleIDs) == 0 { + return make(map[bbs.ArticleID]error), nil + } + + articlesPermInfo, err := schema.GetArticlesPermInfo(boardID, articleIDs) + if err != nil { + return nil, err + } + if articlesPermInfo == nil { + return nil, ErrNoArticle + } + + return checkUserArticlesPermDeletableCore(userID, articlesPermInfo, userBoardPermReadable), nil +} + +func checkUserArticlesPermDeletableCore(userID bbs.UUserID, articlesPermInfo []*schema.ArticlePermInfo, userBoardPerm *UserBoardPermReadable) (articlePermMap map[bbs.ArticleID]error) { + articlePermMap = make(map[bbs.ArticleID]error) + for _, each := range articlesPermInfo { + var err error + if each.IsDeleted { + err = ErrNoArticle + } else if userID == each.Owner { + err = nil + } else if userBoardPerm.IsBM { + err = nil + } else if userBoardPerm.IsSYSOP { + err = nil + } else { + err = ErrInvalidUser + } + articlePermMap[each.ArticleID] = err + } + + return articlePermMap +} + +// CheckUserArticlesPermEditable +// +// articles Editable +func CheckUserArticlesPermEditableDeletable(userID bbs.UUserID, boardID bbs.BBoardID, articleIDs []bbs.ArticleID, userBoardPerm *UserBoardPermReadable) (articlePermEditableMap map[bbs.ArticleID]error, articlePermDeletableMap map[bbs.ArticleID]error, err error) { + if userBoardPerm == nil { + userBoardPerm, err = CheckUserBoardPermReadable(userID, boardID) + if err != nil { + return nil, nil, err + } + } + + if len(articleIDs) == 0 { + return make(map[bbs.ArticleID]error), make(map[bbs.ArticleID]error), nil + } + + articlesPermInfo, err := schema.GetArticlesPermInfo(boardID, articleIDs) + if err != nil { + return nil, nil, err + } + if articlesPermInfo == nil { + return nil, nil, ErrNoArticle + } + + return checkUserArticlesPermEditableCore(userID, articlesPermInfo, userBoardPerm), checkUserArticlesPermDeletableCore(userID, articlesPermInfo, userBoardPerm), nil +} diff --git a/api/user_board_perm.go b/api/user_board_perm.go new file mode 100644 index 00000000..01f37a05 --- /dev/null +++ b/api/user_board_perm.go @@ -0,0 +1,434 @@ +package api + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbs/ptttype" + "github.com/Ptt-official-app/go-pttbbsweb/schema" + "github.com/Ptt-official-app/go-pttbbsweb/types" +) + +type UserBoardPermReadable struct { + IsSYSOP bool + + IsPolice bool + + IsBM bool + + IsBoardFriend bool +} + +type CooldownLimit struct { + NUser int + Posttime int +} + +var COOLDOWN_LIMIT = []*CooldownLimit{ + {NUser: 4000, Posttime: 1}, + {NUser: 2000, Posttime: 2}, + {NUser: 1000, Posttime: 3}, + {NUser: 0, Posttime: 10}, +} + +// CheckUserBoardPermCreatable +func CheckUserBoardPermCreatable(userID bbs.UUserID) (err error) { + userPermInfo, err := schema.GetUserPermInfo(userID) + if err != nil { + return err + } + if userPermInfo == nil { + return ErrInvalidUser + } + + return checkUserBoardPermCreatableCore(userPermInfo) +} + +func checkUserBoardPermCreatableCore(userPermInfo *schema.UserPermInfo) (err error) { + if userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_SYSOP) { + return nil + } + + if userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_BOARD) { + return nil + } + + return ErrPermBoardCreatePermission +} + +// CheckUserBoardPermReadable +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/board.c#L185 +func CheckUserBoardPermReadable(userID bbs.UUserID, boardID bbs.BBoardID) (userBoardPerm *UserBoardPermReadable, err error) { + userPermInfo, err := schema.GetUserPermInfo(userID) + if err != nil { + return nil, err + } + if userPermInfo == nil { + return nil, ErrNoUser + } + + boardPermInfo, err := schema.GetBoardPermInfo(boardID) + if err != nil { + return nil, err + } + if boardPermInfo == nil { + return nil, ErrNoBoard + } + + userBoardInfo, err := schema.FindUserBoard(userID, boardID) + if err != nil { + return nil, err + } + + return checkUserBoardPermReadableCore(userID, boardID, userPermInfo, boardPermInfo, userBoardInfo) +} + +// checkUserBoardPermReadableCore +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/board.c#L185 +func checkUserBoardPermReadableCore(userID bbs.UUserID, boardID bbs.BBoardID, userPermInfo *schema.UserPermInfo, boardPermInfo *schema.BoardPermInfo, userBoardInfo *schema.UserBoard) (userBoardPerm *UserBoardPermReadable, err error) { + // 1. if the user is SYSOP: can read, edit, post. + if userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_SYSOP) { + return &UserBoardPermReadable{ + IsSYSOP: true, + }, nil + } + + // 2. if is POLICE or POLICE_MAN and board is BM-board: can read, edit, post. + if boardPermInfo.Level.HasUserPerm(ptttype.PERM_BM) && userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_POLICE|ptttype.PERM_POLICE_MAN) { + return &UserBoardPermReadable{ + IsPolice: true, + }, nil + } + + // 3. if is BM: can read and edit + isBM, err := checkBoardBM(userID, boardID, boardPermInfo.BMs, boardPermInfo.ParentID) + if err != nil { + return nil, err + } + if isBM { + return &UserBoardPermReadable{ + IsBM: true, + }, nil + } + + // 4. if is hidden board + if boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_HIDE) { + err = checkBoardFriend(userBoardInfo) + if err != nil { + return nil, ErrPermBoardReadHidden + } + } + + // 5. if user blocks board + err = checkBoardBlocked(userBoardInfo) + if err != nil { + return nil, ErrPermBoardReadBlocked + } + + // 6. if user reports board + err = checkBoardReported(userBoardInfo) + if err != nil { + return nil, ErrPermBoardReadReported + } + + // 5. require age over 18 + if boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_OVER18) && !userPermInfo.Over18 { + return nil, ErrPermBoardReadNotOver18 + } + + // 6. board perm meets user perm + if boardPermInfo.Level != 0 && !boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_POSTMASK) && !userPermInfo.Userlevel.HasUserPerm(boardPermInfo.Level) { + return nil, ErrPermBoardReadPermission + } + + // 7. pass + return &UserBoardPermReadable{}, nil +} + +// CheckUserBoardPermPostable +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/cache.c#L209 +func CheckUserBoardPermPostable(userID bbs.UUserID, boardID bbs.BBoardID) (err error) { + userPermInfo, err := schema.GetUserPermInfo(userID) + if err != nil { + return err + } + if userPermInfo == nil { + return ErrInvalidUser + } + + boardPermInfo, err := schema.GetBoardPermInfo(boardID) + if err != nil { + return err + } + if boardPermInfo == nil { + return ErrNoBoard + } + + userBoardInfo, err := schema.FindUserBoard(userID, boardID) + if err != nil { + return err + } + + userBoardPermReadable, err := checkUserBoardPermReadableCore(userID, boardID, userPermInfo, boardPermInfo, userBoardInfo) + if err != nil { + return err + } + + return checkUserBoardPermPostableCore(userID, boardID, userPermInfo, boardPermInfo, userBoardInfo, userBoardPermReadable) +} + +// checkUserBoardPermPostableCore +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/cache.c#L209 +func checkUserBoardPermPostableCore(userID bbs.UUserID, boardID bbs.BBoardID, userPermInfo *schema.UserPermInfo, boardPermInfo *schema.BoardPermInfo, userBoardInfo *schema.UserBoard, userBoardPermReadable *UserBoardPermReadable) (err error) { + err = checkReadOnlyBoard(boardPermInfo) + if err != nil { + return err + } + + // 1. if the user is SYSOP: can post + if userBoardPermReadable.IsSYSOP { + return nil + } + + // 2. if is banned by board + err = checkBannedByBoard(userBoardInfo) + if err != nil { + return err + } + + // 3. if is default-board (SYSOP): can post + if boardID == bbs.BBoardID(ptttype.DEFAULT_BOARD) { + return nil + } + + // 4. if guest posttable + if boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_GUESTPOST) { + return nil + } + + // 5. XXX BM posttable? + /* + if userBoardPermReadable.IsBM { + return nil + } + */ + + // 6. no post permission + if !userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_POST) { + return ErrPermBoardPostPost + } + + // 7. allow only board-friend + // + // 7.1. hidden board already checked in read. + if !boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_HIDE) && boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_RESTRICTEDPOST) { + err = checkBoardFriend(userBoardInfo) + if err != nil { + return ErrPermBoardPostRestricted + } + } + + // 8. violate law + if userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_VIOLATELAW) { + if boardPermInfo.Level.HasUserPerm(ptttype.PERM_VIOLATELAW) { + return nil + } + return ErrPermBoardPostViolateLaw + } + + // 9. board perm meets user perm + if boardPermInfo.Level != 0 && !boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_POSTMASK) && !userPermInfo.Userlevel.HasUserPerm(boardPermInfo.Level) { + return ErrPermBoardPostPermission + } + + // 10. board post restrictions + err = checkBoardRestriction(userPermInfo, boardPermInfo) + if err != nil { + return err + } + + // 11. check cooldown + err = checkCooldown(userID, userPermInfo, boardPermInfo) + if err != nil { + return err + } + + // 12. pass + return nil +} + +// CheckUserBoardPermEditable +func CheckUserBoardPermEditable(userID bbs.UUserID, boardID bbs.BBoardID) (err error) { + userPermInfo, err := schema.GetUserPermInfo(userID) + if err != nil { + return err + } + if userPermInfo == nil { + return ErrInvalidUser + } + + boardPermInfo, err := schema.GetBoardPermInfo(boardID) + if err != nil { + return err + } + if boardPermInfo == nil { + return ErrNoBoard + } + + userBoardInfo, err := schema.FindUserBoard(userID, boardID) + if err != nil { + return err + } + + userBoardPermReadable, err := checkUserBoardPermReadableCore(userID, boardID, userPermInfo, boardPermInfo, userBoardInfo) + if err != nil { + return err + } + + err = checkUserBoardPermPostableCore(userID, boardID, userPermInfo, boardPermInfo, userBoardInfo, userBoardPermReadable) + if err != nil { + return err + } + + if userBoardPermReadable.IsBM || userBoardPermReadable.IsSYSOP { + return nil + } + + return ErrPermBoardEditPermission +} + +func checkBoardFriend(userBoardInfo *schema.UserBoard) (err error) { + if userBoardInfo == nil { + return ErrNotFriend + } + + if !userBoardInfo.BoardFriend { + return ErrNotFriend + } + + return nil +} + +func checkBoardBlocked(userBoardInfo *schema.UserBoard) (err error) { + if userBoardInfo == nil { + return nil + } + + if userBoardInfo.BoardBlocked { + return ErrBoardBlocked + } + + return nil +} + +func checkBoardReported(userBoardInfo *schema.UserBoard) (err error) { + if userBoardInfo == nil { + return nil + } + + if userBoardInfo.BoardReported { + return ErrBoardReported + } + + return nil +} + +// checkReadOnlyBoard +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/board.c#L59 +func checkReadOnlyBoard(boardPermInfo *schema.BoardPermInfo) (err error) { + if boardPermInfo.Brdname == ptttype.BN_ALLPOST_s || + boardPermInfo.Brdname == ptttype.BN_SECURITY_s || + boardPermInfo.Brdname == ptttype.BN_ALLHIDPOST_s { + return ErrPermPostReadOnly + } + + return nil +} + +// checkBannedByBoard +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/acl.c#L75 +func checkBannedByBoard(userBoardInfo *schema.UserBoard) (err error) { + nowNanoTS := types.NowNanoTS() + if userBoardInfo.BoardBucketExpireNanoTS > nowNanoTS { + return ErrBoardBucket + } + + return nil +} + +func checkBoardBM(userID bbs.UUserID, boardID bbs.BBoardID, bms []bbs.UUserID, boardParentID bbs.BBoardID) (ret bool, err error) { + for _, each := range bms { + if userID == each { + return true, nil + } + } + + for boardParentID != "" { + boardPermInfo, err := schema.GetBoardPermInfo(boardParentID) + if err != nil { + return false, err + } + + for _, each := range boardPermInfo.BMs { + if userID == each { + return true, nil + } + } + + boardParentID = boardPermInfo.ParentID + } + + return false, nil +} + +// checkBoardRestriction +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/cal.c#L6 +func checkBoardRestriction(userPermInfo *schema.UserPermInfo, boardPermInfo *schema.BoardPermInfo) (err error) { + if userPermInfo.Numlogindays/10 < boardPermInfo.PostLimitLogins { + return ErrPermBoardPostLoginDays + } + + if userPermInfo.BadPost > (255 - boardPermInfo.PostLimitBadpost) { + return ErrPermBoardPostPostLimit + } + return nil +} + +// checkCooldown +// +// https://github.com/ptt/pttbbs/blob/master/mbbsd/bbs.c#L4244 +func checkCooldown(userID bbs.UUserID, userPermInfo *schema.UserPermInfo, boardPermInfo *schema.BoardPermInfo) (err error) { + nowNanoTS := types.NowNanoTS() + diffNanoTS := userPermInfo.CooldownNanoTS - nowNanoTS + if diffNanoTS < 0 { // cooldown expired + if userPermInfo.Posttime > 0 { + _ = schema.UpdateUserPosttime(userID, 0) + } + return nil + } + + if userPermInfo.Userlevel.HasUserPerm(ptttype.PERM_SYSOP) { + return nil + } + + if boardPermInfo.BrdAttr.HasPerm(ptttype.BRD_COOLDOWN) { + return ErrBoardCooldown(diffNanoTS) + } + + if userPermInfo.Posttime == types.POSTTIME_REJECT { + return ErrBoardPosttime(diffNanoTS) + } + + for _, each := range COOLDOWN_LIMIT { + if boardPermInfo.NUser >= each.NUser && userPermInfo.Posttime >= each.Posttime { + return ErrFloodReject(diffNanoTS) + } + } + + return nil +} diff --git a/apitypes/article_summary.go b/apitypes/article_summary.go index 889d396d..87233dfe 100644 --- a/apitypes/article_summary.go +++ b/apitypes/article_summary.go @@ -31,6 +31,9 @@ type ArticleSummary struct { SubjectType ptttype.SubjectType `json:"subject_type"` TokenUser bbs.UUserID `json:"tokenuser"` + + Editable bool `json:"editable"` + Deletable bool `json:"deletable"` } func ToFTitle(title string) string { diff --git a/apitypes/errors.go b/apitypes/errors.go index 463edc21..6a047b2a 100644 --- a/apitypes/errors.go +++ b/apitypes/errors.go @@ -2,4 +2,7 @@ package apitypes import "errors" -var ErrInvalidIdx = errors.New("invalid idx") +var ( + ErrInvalidIdx = errors.New("invalid idx") + ErrInvalidBoardArticleID = errors.New("invalid board-article-id") +) diff --git a/cron/load_article_details.go b/cron/load_article_details.go index 5e6309e0..d89b67f9 100644 --- a/cron/load_article_details.go +++ b/cron/load_article_details.go @@ -56,7 +56,6 @@ func LoadArticleDetails() (err error) { if newNextBrdname == "" { logrus.Infof("cron.LoadArticleDetails: load %v boards", count) return nil - } nextBrdname = newNextBrdname @@ -80,7 +79,6 @@ func loadArticleDetails(boardID bbs.BBoardID) (err error) { articleDetailSummaries = articleDetailSummaries[:N_ARTICLE_DETAILS] } - origCount := count for _, each := range articleDetailSummaries { if each.MTime <= each.ContentMTime && each.MTime < each.ContentUpdateNanoTS { continue @@ -92,12 +90,14 @@ func loadArticleDetails(boardID bbs.BBoardID) (err error) { } } - if origCount != count { - logrus.Infof("cron.loadArticleDetails: bid: %v count: %v", boardID, count) - } + /* + if origCount != count { + logrus.Infof("cron.loadArticleDetails: bid: %v count: %v", boardID, count) + } + */ if newNextIdx == "" { - logrus.Infof("cron.loadArticleDetails: bid: %v load %v articles", boardID, count) + // logrus.Infof("cron.loadArticleDetails: bid: %v load %v articles", boardID, count) return nil } diff --git a/cron/load_general_articles.go b/cron/load_general_articles.go index ff24b392..3b359d14 100644 --- a/cron/load_general_articles.go +++ b/cron/load_general_articles.go @@ -77,10 +77,10 @@ func loadGeneralArticles(boardID bbs.BBoardID) (err error) { } count += len(articleSummaries) - logrus.Infof("cron.LoadGeneralArticles: bid: %v count: %v", boardID, count) + // logrus.Infof("cron.LoadGeneralArticles: bid: %v count: %v", boardID, count) if newNextIdx == INVALID_LOAD_GENERAL_ARTICLES_NEXT_IDX { - logrus.Infof("cron.LoadGeneralArticles: bid: %v load %v articles", boardID, count) + // logrus.Infof("cron.LoadGeneralArticles: bid: %v load %v articles", boardID, count) break } diff --git a/cron/load_general_articles_test.go b/cron/load_general_articles_test.go index 4f73feee..47fc9c20 100644 --- a/cron/load_general_articles_test.go +++ b/cron/load_general_articles_test.go @@ -15,8 +15,10 @@ func Test_loadGeneralArticles(t *testing.T) { expected0 := []*schema.ArticleSummaryWithRegex{ { // 0 - BBoardID: "10_WhoAmI", - ArticleID: "1UT16-xG", + BBoardID: "10_WhoAmI", + ArticleID: "1UT16-xG", + BoardArticleID: "10_WhoAmI:1UT16-xG", + IsDeleted: false, CreateTime: 1584665022000000000, MTime: 1644506386000000000, @@ -31,8 +33,10 @@ func Test_loadGeneralArticles(t *testing.T) { TitleRegex: []string{"為", "何", "打", "麻", "將", "叫", "賭", "博", "但", "買", "股", "票", "叫", "投", "資", "?", "為何", "何打", "打麻", "麻將", "將叫", "叫賭", "賭博", "博但", "但買", "買股", "股票", "票叫", "叫投", "投資", "資?", "為何打", "何打麻", "打麻將", "麻將叫", "將叫賭", "叫賭博", "賭博但", "博但買", "但買股", "買股票", "股票叫", "票叫投", "叫投資", "投資?", "為何打麻", "何打麻將", "打麻將叫", "麻將叫賭", "將叫賭博", "叫賭博但", "賭博但買", "博但買股", "但買股票", "買股票叫", "股票叫投", "票叫投資", "叫投資?", "為何打麻將", "何打麻將叫", "打麻將叫賭", "麻將叫賭博", "將叫賭博但", "叫賭博但買", "賭博但買股", "博但買股票", "但買股票叫", "買股票叫投", "股票叫投資", "票叫投資?"}, }, { // 1 - BBoardID: "10_WhoAmI", - ArticleID: "1VtWRel9", + BBoardID: "10_WhoAmI", + ArticleID: "1VtWRel9", + BoardArticleID: "10_WhoAmI:1VtWRel9", + IsDeleted: false, CreateTime: 1608386280000000000, MTime: 1608386280000000000, @@ -47,8 +51,10 @@ func Test_loadGeneralArticles(t *testing.T) { TitleRegex: []string{"測", "試", "一", "下", "特", "殊", "字", "~", "測試", "試一", "一下", "下特", "特殊", "殊字", "字~", "測試一", "試一下", "一下特", "下特殊", "特殊字", "殊字~", "測試一下", "試一下特", "一下特殊", "下特殊字", "特殊字~", "測試一下特", "試一下特殊", "一下特殊字", "下特殊字~"}, }, { // 2 - BBoardID: "10_WhoAmI", - ArticleID: "1VtW-QXT", + BBoardID: "10_WhoAmI", + ArticleID: "1VtW-QXT", + BoardArticleID: "10_WhoAmI:1VtW-QXT", + IsDeleted: false, CreateTime: 1608388506000000000, MTime: 1608386280000000000, @@ -63,8 +69,10 @@ func Test_loadGeneralArticles(t *testing.T) { TitleRegex: []string{"所", "以", "特", "殊", "字", "真", "的", "是", "有", "綠", "色", "的", "∼", "所以", "以特", "特殊", "殊字", "字真", "真的", "的是", "是有", "有綠", "綠色", "色的", "的∼", "所以特", "以特殊", "特殊字", "殊字真", "字真的", "真的是", "的是有", "是有綠", "有綠色", "綠色的", "色的∼", "所以特殊", "以特殊字", "特殊字真", "殊字真的", "字真的是", "真的是有", "的是有綠", "是有綠色", "有綠色的", "綠色的∼", "所以特殊字", "以特殊字真", "特殊字真的", "殊字真的是", "字真的是有", "真的是有綠", "的是有綠色", "是有綠色的", "有綠色的∼"}, }, { // 3 - BBoardID: "10_WhoAmI", - ArticleID: "1Vo_N0CD", + BBoardID: "10_WhoAmI", + ArticleID: "1Vo_N0CD", + BoardArticleID: "10_WhoAmI:1Vo_N0CD", + IsDeleted: false, CreateTime: 1607202240000000000, MTime: 1607202240000000000, @@ -79,8 +87,10 @@ func Test_loadGeneralArticles(t *testing.T) { TitleRegex: []string{"T", "V", "B", "S", "六", "都", "民", "調", " ", "侯", "奪", "冠", "、", "盧", "升", "第", "四", "、", "柯", "墊", "底", "TV", "VB", "BS", "S六", "六都", "都民", "民調", "調 ", " 侯", "侯奪", "奪冠", "冠、", "、盧", "盧升", "升第", "第四", "四、", "、柯", "柯墊", "墊底", "TVB", "VBS", "BS六", "S六都", "六都民", "都民調", "民調 ", "調 侯", " 侯奪", "侯奪冠", "奪冠、", "冠、盧", "、盧升", "盧升第", "升第四", "第四、", "四、柯", "、柯墊", "柯墊底", "TVBS", "VBS六", "BS六都", "S六都民", "六都民調", "都民調 ", "民調 侯", "調 侯奪", " 侯奪冠", "侯奪冠、", "奪冠、盧", "冠、盧升", "、盧升第", "盧升第四", "升第四、", "第四、柯", "四、柯墊", "、柯墊底", "TVBS六", "VBS六都", "BS六都民", "S六都民調", "六都民調 ", "都民調 侯", "民調 侯奪", "調 侯奪冠", " 侯奪冠、", "侯奪冠、盧", "奪冠、盧升", "冠、盧升第", "、盧升第四", "盧升第四、", "升第四、柯", "第四、柯墊", "四、柯墊底"}, }, { // 4 - BBoardID: "10_WhoAmI", - ArticleID: "1VrooM21", + BBoardID: "10_WhoAmI", + ArticleID: "1VrooM21", + BoardArticleID: "10_WhoAmI:1VrooM21", + IsDeleted: false, CreateTime: 1607937174000000000, MTime: 1607937100000000000, @@ -95,8 +105,10 @@ func Test_loadGeneralArticles(t *testing.T) { TitleRegex: []string{"新", "書", "的", "情", "報", "新書", "書的", "的情", "情報", "新書的", "書的情", "的情報", "新書的情", "書的情報", "新書的情報"}, }, { // 5 - BBoardID: "10_WhoAmI", - ArticleID: "19bWBI4Z", + BBoardID: "10_WhoAmI", + ArticleID: "19bWBI4Z", + BoardArticleID: "10_WhoAmI:19bWBI4Z", + IsDeleted: false, CreateTime: 1234567890000000000, MTime: 1234567889000000000, @@ -111,8 +123,10 @@ func Test_loadGeneralArticles(t *testing.T) { TitleRegex: []string{"然", "後", "呢", "?", "~", "然後", "後呢", "呢?", "?~", "然後呢", "後呢?", "呢?~", "然後呢?", "後呢?~", "然後呢?~"}, }, { // 6 - BBoardID: "10_WhoAmI", - ArticleID: "19bUG021", + BBoardID: "10_WhoAmI", + ArticleID: "19bUG021", + BoardArticleID: "10_WhoAmI:19bUG021", + IsDeleted: false, CreateTime: 1234560000000000000, MTime: 1234560000000000000, diff --git a/cron/load_man_articles.go b/cron/load_man_articles.go index 9abc93f8..6a0462f4 100644 --- a/cron/load_man_articles.go +++ b/cron/load_man_articles.go @@ -72,13 +72,13 @@ func LoadManArticles() (err error) { } func loadManArticles(boardID bbs.BBoardID) (err error) { - count, err := loadManArticlesCore(boardID, "") + _, err = loadManArticlesCore(boardID, "") if err != nil { logrus.Errorf("cron.loadManArticles: unable to loadManArticles: e: %v", err) return err } - logrus.Infof("cron.loadManArticles: bid: %v count: %v", boardID, count) + // logrus.Infof("cron.loadManArticles: bid: %v count: %v", boardID, count) return nil } diff --git a/dbcs/testcases_test.go b/dbcs/testcases_test.go index 4d5ca56d..6000ec86 100644 --- a/dbcs/testcases_test.go +++ b/dbcs/testcases_test.go @@ -1,8 +1,6 @@ package dbcs import ( - "io" - "io/ioutil" "os" "github.com/sirupsen/logrus" @@ -49,27 +47,19 @@ func initTest() { func loadTest(filename string) (contentAll []byte, content []byte, signature []byte, recommend []byte, firstComments []byte, theRestComments []byte) { // content-all fullFilename := "testcase/" + filename - file0, err := os.Open(fullFilename) + contentAll, err := os.ReadFile(fullFilename) if err != nil { logrus.Errorf("loadTest: unable to open: filename: %v e: %v", filename, err) return nil, nil, nil, nil, nil, nil } - defer file0.Close() - - r := io.Reader(file0) - contentAll, _ = ioutil.ReadAll(r) // content fullFilename = "testcase/" + filename + ".content" - file1, err := os.Open(fullFilename) + content, err = os.ReadFile(fullFilename) if err != nil { logrus.Errorf("loadTest: unable to open content: filename : %v e: %v", filename, err) return nil, nil, nil, nil, nil, nil } - defer file1.Close() - - r = io.Reader(file1) - content, _ = ioutil.ReadAll(r) if len(content) == 0 { content = nil @@ -77,15 +67,11 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // signature fullFilename = "testcase/" + filename + ".signature" - file2, err := os.Open(fullFilename) + signature, err = os.ReadFile(fullFilename) if err != nil { logrus.Errorf("loadTest: unable to open signature: filename : %v e: %v", filename, err) return nil, nil, nil, nil, nil, nil } - defer file2.Close() - - r = io.Reader(file2) - signature, _ = ioutil.ReadAll(r) if len(signature) == 0 { signature = nil @@ -93,14 +79,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // recommend fullFilename = "testcase/" + filename + ".recommend" - file3, err := os.Open(fullFilename) + recommend, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file3.Close() - - r = io.Reader(file3) - recommend, _ = ioutil.ReadAll(r) if len(recommend) == 0 { recommend = nil @@ -108,14 +90,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // firstComments fullFilename = "testcase/" + filename + ".firstComments" - file4, err := os.Open(fullFilename) + firstComments, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file4.Close() - - r = io.Reader(file4) - firstComments, _ = ioutil.ReadAll(r) if len(firstComments) == 0 { firstComments = nil @@ -123,14 +101,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // theRestComments fullFilename = "testcase/" + filename + ".theRestComments" - file5, err := os.Open(fullFilename) + theRestComments, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file5.Close() - - r = io.Reader(file5) - theRestComments, _ = ioutil.ReadAll(r) if len(theRestComments) == 0 { theRestComments = nil diff --git a/fav/fav_test.go b/fav/fav_test.go index 2926096e..6b146028 100644 --- a/fav/fav_test.go +++ b/fav/fav_test.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/binary" "io" - "io/ioutil" + "os" "reflect" "sync" "testing" @@ -91,13 +91,13 @@ func TestFavRaw_AddBoard(t *testing.T) { filename3 := "./testcase/home1/t/testUser3/.fav" - theBytes3, _ := ioutil.ReadFile(filename3) + theBytes3, _ := os.ReadFile(filename3) buf3 := bytes.NewReader(theBytes3) filename4 := "./testcase/home1/t/testUser4/.fav" - theBytes4, _ := ioutil.ReadFile(filename4) + theBytes4, _ := os.ReadFile(filename4) buf4 := bytes.NewReader(theBytes4) @@ -386,25 +386,25 @@ func TestReadFavrec(t *testing.T) { filename0 := "./testcase/home1/t/testUser/.fav" - theBytes0, _ := ioutil.ReadFile(filename0) + theBytes0, _ := os.ReadFile(filename0) buf0 := bytes.NewReader(theBytes0) filename1 := "./testcase/home1/t/testUser2/.fav" - theBytes1, _ := ioutil.ReadFile(filename1) + theBytes1, _ := os.ReadFile(filename1) buf1 := bytes.NewReader(theBytes1) filename3 := "./testcase/home1/t/testUser3/.fav" - theBytes3, _ := ioutil.ReadFile(filename3) + theBytes3, _ := os.ReadFile(filename3) buf3 := bytes.NewReader(theBytes3) filename4 := "./testcase/home1/t/testUser4/.fav" - theBytes4, _ := ioutil.ReadFile(filename4) + theBytes4, _ := os.ReadFile(filename4) buf4 := bytes.NewReader(theBytes4) @@ -466,11 +466,11 @@ func TestFav_WriteFavrec(t *testing.T) { filename := "./testcase/home1/t/testUser/.fav" - theBytes0, _ := ioutil.ReadFile(filename) + theBytes0, _ := os.ReadFile(filename) filename1 := "./testcase/home1/t/testUser2/.fav" - theBytes1, _ := ioutil.ReadFile(filename1) + theBytes1, _ := os.ReadFile(filename1) tests := []struct { name string @@ -510,7 +510,7 @@ func TestFav_LocateFav(t *testing.T) { filename0 := "./testcase/home1/t/testUser/.fav" - theBytes0, _ := ioutil.ReadFile(filename0) + theBytes0, _ := os.ReadFile(filename0) buf0 := bytes.NewReader(theBytes0) @@ -576,7 +576,7 @@ func TestFav_DeleteIdx(t *testing.T) { filename0 := "./testcase/home1/t/testUser/.fav" - theBytes0, _ := ioutil.ReadFile(filename0) + theBytes0, _ := os.ReadFile(filename0) // fav0 buf0 := bytes.NewReader(theBytes0) diff --git a/mockhttp/load_general_articles2.go b/mockhttp/load_general_articles2.go index f00f4d01..930e20d5 100644 --- a/mockhttp/load_general_articles2.go +++ b/mockhttp/load_general_articles2.go @@ -38,7 +38,7 @@ func LoadGeneralArticles2(params *api.LoadGeneralArticlesParams) (ret *api.LoadG BBoardID: bbs.BBoardID("10_WhoAmI"), ArticleID: bbs.ArticleID("1VtWRel9"), IsDeleted: false, - Filename: "M.1234567890.A.123", + Filename: "M.1608386280.A.BC9", CreateTime: types.Time4(1608386280), MTime: types.Time4(1608386280), Recommend: 8, @@ -48,7 +48,7 @@ func LoadGeneralArticles2(params *api.LoadGeneralArticlesParams) (ret *api.LoadG Money: 3, Filemode: 0, Read: false, - Idx: "1234567890@1VtWRel9", + Idx: "1608386280@1VtWRel9", RealTitle: []byte{0xb5, 0x4d, 0xab, 0xe1, 0xa9, 0x4f, 0xa1, 0x48, 0xa1, 0xe3}, } } else if params.StartIdx == "1607202240@1Vo_N0CD" { diff --git a/pttbbs b/pttbbs index 8a6bd7a2..c4f6a291 160000 --- a/pttbbs +++ b/pttbbs @@ -1 +1 @@ -Subproject commit 8a6bd7a2430949f19c77503398cbd8353c69b7ff +Subproject commit c4f6a2910ecde07f62445c38ecc980f5442f4838 diff --git a/queue/testcases_test.go b/queue/testcases_test.go index 45ae93f9..1dd26311 100644 --- a/queue/testcases_test.go +++ b/queue/testcases_test.go @@ -1,8 +1,6 @@ package queue import ( - "io" - "io/ioutil" "os" ) @@ -18,25 +16,17 @@ func initTest() { func loadTest(filename string) (contentAll []byte, content []byte, signature []byte, recommend []byte, firstComments []byte, theRestComments []byte) { // content-all fullFilename := "testcase/" + filename - file0, err := os.Open(fullFilename) + contentAll, err := os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file0.Close() - - r := io.Reader(file0) - contentAll, _ = ioutil.ReadAll(r) // content fullFilename = "testcase/" + filename + ".content" - file1, err := os.Open(fullFilename) + content, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file1.Close() - - r = io.Reader(file1) - content, _ = ioutil.ReadAll(r) if len(content) == 0 { content = nil @@ -44,14 +34,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // signature fullFilename = "testcase/" + filename + ".signature" - file2, err := os.Open(fullFilename) + signature, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file2.Close() - - r = io.Reader(file2) - signature, _ = ioutil.ReadAll(r) if len(signature) == 0 { signature = nil @@ -59,14 +45,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // recommend fullFilename = "testcase/" + filename + ".recommend" - file3, err := os.Open(fullFilename) + recommend, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file3.Close() - - r = io.Reader(file3) - recommend, _ = ioutil.ReadAll(r) if len(recommend) == 0 { recommend = nil @@ -74,14 +56,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // firstComments fullFilename = "testcase/" + filename + ".firstComments" - file4, err := os.Open(fullFilename) + firstComments, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file4.Close() - - r = io.Reader(file4) - firstComments, _ = ioutil.ReadAll(r) if len(firstComments) == 0 { firstComments = nil @@ -89,14 +67,10 @@ func loadTest(filename string) (contentAll []byte, content []byte, signature []b // theRestComments fullFilename = "testcase/" + filename + ".theRestComments" - file5, err := os.Open(fullFilename) + theRestComments, err = os.ReadFile(fullFilename) if err != nil { return nil, nil, nil, nil, nil, nil } - defer file5.Close() - - r = io.Reader(file5) - theRestComments, _ = ioutil.ReadAll(r) if len(theRestComments) == 0 { theRestComments = nil diff --git a/schema/article.go b/schema/article.go index d1a66f42..e4d49644 100644 --- a/schema/article.go +++ b/schema/article.go @@ -11,9 +11,11 @@ import ( var Article_c *db.Collection type Article struct { - Version int `bson:"version"` - BBoardID bbs.BBoardID `bson:"bid"` // - ArticleID bbs.ArticleID `bson:"aid"` // + Version int `bson:"version"` + BBoardID bbs.BBoardID `bson:"bid"` // + ArticleID bbs.ArticleID `bson:"aid"` // + BoardArticleID types.BoardArticleID `bson:"baid"` + IsDeleted bool `bson:"deleted,omitempty"` // Filename string `bson:"filename"` // CreateTime types.NanoTS `bson:"create_time_nano_ts"` // @@ -65,8 +67,10 @@ type Article struct { var EMPTY_ARTICLE = &Article{} var ( // bson-name - ARTICLE_BBOARD_ID_b = getBSONName(EMPTY_ARTICLE, "BBoardID") - ARTICLE_ARTICLE_ID_b = getBSONName(EMPTY_ARTICLE, "ArticleID") + ARTICLE_BBOARD_ID_b = getBSONName(EMPTY_ARTICLE, "BBoardID") + ARTICLE_ARTICLE_ID_b = getBSONName(EMPTY_ARTICLE, "ArticleID") + ARTICLE_BOARD_ARTICLE_ID_b = getBSONName(EMPTY_ARTICLE, "BoardArticleID") + ARTICLE_IS_DELETED_b = getBSONName(EMPTY_ARTICLE, "IsDeleted") ARTICLE_FILENAME_b = getBSONName(EMPTY_ARTICLE, "Filename") ARTICLE_CREATE_TIME_b = getBSONName(EMPTY_ARTICLE, "CreateTime") @@ -157,6 +161,11 @@ func assertArticleFields() error { // article-rank + // article-perm-info + if err := assertFields(EMPTY_ARTICLE, EMPTY_ARTICLE_PERM_INFO); err != nil { + return err + } + return nil } @@ -168,6 +177,16 @@ type ArticleQuery struct { var EMPTY_ARTICLE_QUERY = &ArticleQuery{} +type ArticleCreateQuery struct { + BBoardID bbs.BBoardID `bson:"bid"` + ArticleID bbs.ArticleID `bson:"aid"` + BoardArticleID types.BoardArticleID `bson:"baid"` + + IsDeleted interface{} `bson:"deleted,omitempty"` // +} + +var EMPTY_ARTICLE_CREATE_QUERY = &ArticleCreateQuery{} + func ResetArticleIsBottom(boardID bbs.BBoardID) (err error) { query := bson.M{ ARTICLE_BBOARD_ID_b: boardID, diff --git a/schema/article_content_info.go b/schema/article_content_info.go index 48c4c8c9..28e43b3d 100644 --- a/schema/article_content_info.go +++ b/schema/article_content_info.go @@ -76,10 +76,13 @@ func contentBlocksToContent(contentBlocks []*ContentBlock) (content [][]*types.R return content } -func UpdateArticleContentInfo(bboardID bbs.BBoardID, articleID bbs.ArticleID, contentInfo *ArticleContentInfo) (err error) { +func UpdateArticleContentInfo(boardID bbs.BBoardID, articleID bbs.ArticleID, contentInfo *ArticleContentInfo) (err error) { + boardArticleID := types.ToBoardArticleID(boardID, articleID) + query := bson.M{ - ARTICLE_BBOARD_ID_b: bboardID, - ARTICLE_ARTICLE_ID_b: articleID, + ARTICLE_BBOARD_ID_b: boardID, + ARTICLE_ARTICLE_ID_b: articleID, + ARTICLE_BOARD_ARTICLE_ID_b: boardArticleID, } r, err := Article_c.CreateOnly(query, contentInfo) @@ -93,7 +96,7 @@ func UpdateArticleContentInfo(bboardID bbs.BBoardID, articleID bbs.ArticleID, co query = bson.M{ "$or": bson.A{ bson.M{ - ARTICLE_BBOARD_ID_b: bboardID, + ARTICLE_BBOARD_ID_b: boardID, ARTICLE_ARTICLE_ID_b: articleID, ARTICLE_CONTENT_UPDATE_NANO_TS_b: bson.M{ "$exists": false, @@ -102,7 +105,7 @@ func UpdateArticleContentInfo(bboardID bbs.BBoardID, articleID bbs.ArticleID, co ARTICLE_IS_DELETED_b: bson.M{"$exists": false}, }, bson.M{ - ARTICLE_BBOARD_ID_b: bboardID, + ARTICLE_BBOARD_ID_b: boardID, ARTICLE_ARTICLE_ID_b: articleID, ARTICLE_CONTENT_UPDATE_NANO_TS_b: bson.M{ "$lt": contentInfo.ContentUpdateNanoTS, diff --git a/schema/article_detail_summary.go b/schema/article_detail_summary.go index a54d3d09..70347a7c 100644 --- a/schema/article_detail_summary.go +++ b/schema/article_detail_summary.go @@ -10,11 +10,13 @@ import ( // ArticleDetailSummary type ArticleDetailSummary struct { - BBoardID bbs.BBoardID `bson:"bid"` - ArticleID bbs.ArticleID `bson:"aid"` - IsDeleted bool `bson:"deleted,omitempty"` - CreateTime types.NanoTS `bson:"create_time_nano_ts"` - MTime types.NanoTS `bson:"mtime_nano_ts"` + BBoardID bbs.BBoardID `bson:"bid"` + ArticleID bbs.ArticleID `bson:"aid"` + BoardArticleID types.BoardArticleID `bson:"baid"` + + IsDeleted bool `bson:"deleted,omitempty"` + CreateTime types.NanoTS `bson:"create_time_nano_ts"` + MTime types.NanoTS `bson:"mtime_nano_ts"` Recommend int `bson:"recommend"` Owner bbs.UUserID `bson:"owner"` diff --git a/schema/article_detail_summary_test.go b/schema/article_detail_summary_test.go index 997514fa..f26482dd 100644 --- a/schema/article_detail_summary_test.go +++ b/schema/article_detail_summary_test.go @@ -15,8 +15,10 @@ func TestGetArticleDetailSummary(t *testing.T) { updateNanoTS := types.NanoTS(1234567890000000000) articleSummary0 := &ArticleSummary{ - BBoardID: bbs.BBoardID("board0"), - ArticleID: bbs.ArticleID("article0"), + BBoardID: bbs.BBoardID("board0"), + ArticleID: bbs.ArticleID("article0"), + BoardArticleID: "board0:article0", + IsDeleted: false, CreateTime: types.NanoTS(1234567890000000000), MTime: types.NanoTS(1234567889000000000), @@ -43,8 +45,10 @@ func TestGetArticleDetailSummary(t *testing.T) { _ = UpdateArticleContentInfo(bbs.BBoardID("board0"), bbs.ArticleID("article0"), articleContent) expected0 := &ArticleDetailSummary{ - BBoardID: bbs.BBoardID("board0"), - ArticleID: bbs.ArticleID("article0"), + BBoardID: bbs.BBoardID("board0"), + ArticleID: bbs.ArticleID("article0"), + BoardArticleID: "board0:article0", + IsDeleted: false, CreateTime: types.NanoTS(1234567890000000000), MTime: types.NanoTS(1234567889000000000), @@ -109,8 +113,10 @@ func TestGetArticleDetailSummaries(t *testing.T) { updateNanoTS := types.NanoTS(1234567890000000000) articleSummary0 := &ArticleSummary{ - BBoardID: bbs.BBoardID("board0"), - ArticleID: bbs.ArticleID("article0"), + BBoardID: bbs.BBoardID("board0"), + ArticleID: bbs.ArticleID("article0"), + BoardArticleID: "board0:article0", + IsDeleted: false, CreateTime: types.NanoTS(1234567890000000000), MTime: types.NanoTS(1234567889000000000), @@ -138,8 +144,10 @@ func TestGetArticleDetailSummaries(t *testing.T) { _ = UpdateArticleContentInfo(bbs.BBoardID("board0"), bbs.ArticleID("article0"), articleContent0) articleSummary1 := &ArticleSummary{ - BBoardID: bbs.BBoardID("board0"), - ArticleID: bbs.ArticleID("article1"), + BBoardID: bbs.BBoardID("board0"), + ArticleID: bbs.ArticleID("article1"), + BoardArticleID: "board0:article1", + IsDeleted: false, CreateTime: types.NanoTS(1234567891000000000), MTime: types.NanoTS(1234567892000000000), @@ -167,8 +175,10 @@ func TestGetArticleDetailSummaries(t *testing.T) { _ = UpdateArticleContentInfo(bbs.BBoardID("board0"), bbs.ArticleID("article1"), articleContent1) expected0 := &ArticleDetailSummary{ - BBoardID: bbs.BBoardID("board0"), - ArticleID: bbs.ArticleID("article0"), + BBoardID: bbs.BBoardID("board0"), + ArticleID: bbs.ArticleID("article0"), + BoardArticleID: "board0:article0", + IsDeleted: false, CreateTime: types.NanoTS(1234567890000000000), MTime: types.NanoTS(1234567889000000000), @@ -191,8 +201,10 @@ func TestGetArticleDetailSummaries(t *testing.T) { } expected1 := &ArticleDetailSummary{ - BBoardID: bbs.BBoardID("board0"), - ArticleID: bbs.ArticleID("article1"), + BBoardID: bbs.BBoardID("board0"), + ArticleID: bbs.ArticleID("article1"), + BoardArticleID: "board0:article1", + IsDeleted: false, CreateTime: types.NanoTS(1234567891000000000), MTime: types.NanoTS(1234567892000000000), diff --git a/schema/article_perm_info.go b/schema/article_perm_info.go new file mode 100644 index 00000000..b29cd525 --- /dev/null +++ b/schema/article_perm_info.go @@ -0,0 +1,57 @@ +package schema + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbsweb/types" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type ArticlePermInfo struct { + BBoardID bbs.BBoardID `bson:"bid"` // + ArticleID bbs.ArticleID `bson:"aid"` // + BoardArticleID types.BoardArticleID `bson:"baid"` + + Owner bbs.UUserID `bson:"owner"` // + + IsDeleted bool `bson:"deleted,omitempty"` // +} + +var ( + EMPTY_ARTICLE_PERM_INFO = &ArticlePermInfo{} + articlePermInfoFields = getFields(EMPTY_ARTICLE, EMPTY_ARTICLE_PERM_INFO) +) + +func GetArticlePermInfo(boardID bbs.BBoardID, articleID bbs.ArticleID) (articlePermInfo *ArticlePermInfo, err error) { + query := bson.M{ + ARTICLE_BBOARD_ID_b: boardID, + ARTICLE_ARTICLE_ID_b: articleID, + } + + err = Article_c.FindOne(query, &articlePermInfo, articlePermInfoFields) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + return articlePermInfo, nil +} + +func GetArticlesPermInfo(boardID bbs.BBoardID, articleIDs []bbs.ArticleID) (articlesPermInfo []*ArticlePermInfo, err error) { + query := bson.M{ + ARTICLE_BBOARD_ID_b: boardID, + ARTICLE_ARTICLE_ID_b: bson.M{ + "$in": articleIDs, + }, + } + + err = Article_c.Find(query, 0, &articlesPermInfo, articlePermInfoFields, nil) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + return articlesPermInfo, nil +} diff --git a/schema/article_summary.go b/schema/article_summary.go index e78095f3..358dc595 100644 --- a/schema/article_summary.go +++ b/schema/article_summary.go @@ -14,11 +14,13 @@ import ( // ArticleSummary type ArticleSummary struct { // ArticleSummary - BBoardID bbs.BBoardID `bson:"bid"` - ArticleID bbs.ArticleID `bson:"aid"` - IsDeleted bool `bson:"deleted,omitempty"` - CreateTime types.NanoTS `bson:"create_time_nano_ts"` - MTime types.NanoTS `bson:"mtime_nano_ts"` + BBoardID bbs.BBoardID `bson:"bid"` + ArticleID bbs.ArticleID `bson:"aid"` + BoardArticleID types.BoardArticleID `bson:"baid"` + + IsDeleted bool `bson:"deleted,omitempty"` + CreateTime types.NanoTS `bson:"create_time_nano_ts"` + MTime types.NanoTS `bson:"mtime_nano_ts"` Recommend int `bson:"recommend"` Owner bbs.UUserID `bson:"owner"` @@ -64,10 +66,10 @@ func GetArticleSummary(bboardID bbs.BBoardID, articleID bbs.ArticleID) (result * return result, nil } -func GetArticleSummariesByArticleIDs(articleIDs []bbs.ArticleID) (result []*ArticleSummary, err error) { +func GetArticleSummariesByBoardArticleIDs(boardArticleIDs []types.BoardArticleID) (result []*ArticleSummary, err error) { query := bson.M{ - ARTICLE_ARTICLE_ID_b: bson.M{ - "$in": articleIDs, + ARTICLE_BOARD_ARTICLE_ID_b: bson.M{ + "$in": boardArticleIDs, }, } @@ -395,8 +397,10 @@ func getArticleSummariesByRegexIsValidTitle(title string, keywordList []string, // no n_comments in bbs.ArticleSummary from backend. func NewArticleSummary(a_b *bbs.ArticleSummary, updateNanoTS types.NanoTS) *ArticleSummary { return &ArticleSummary{ - BBoardID: a_b.BBoardID, - ArticleID: a_b.ArticleID, + BBoardID: a_b.BBoardID, + ArticleID: a_b.ArticleID, + BoardArticleID: types.ToBoardArticleID(a_b.BBoardID, a_b.ArticleID), + IsDeleted: a_b.IsDeleted, CreateTime: types.Time4ToNanoTS(a_b.CreateTime), MTime: types.Time4ToNanoTS(a_b.MTime), diff --git a/schema/article_summary_test.go b/schema/article_summary_test.go index 5455149c..6a717e58 100644 --- a/schema/article_summary_test.go +++ b/schema/article_summary_test.go @@ -30,8 +30,10 @@ func TestUpdateArticleSummaries(t *testing.T) { query0 := &ArticleQuery{BBoardID: bbs.BBoardID("10_WhoAmI"), ArticleID: bbs.ArticleID("19bWBI4Z")} articleSummary0 := &ArticleSummary{ - BBoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("19bWBI4Z"), + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("19bWBI4Z"), + BoardArticleID: types.BoardArticleID("10_WhoAmI:19bWBI4Z"), + IsDeleted: false, CreateTime: types.NanoTS(1234567890000000000), MTime: types.NanoTS(1234567889000000000), @@ -48,8 +50,10 @@ func TestUpdateArticleSummaries(t *testing.T) { } articleSummary1 := &ArticleSummary{ - BBoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("1VrooM21"), + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VrooM21"), + BoardArticleID: types.BoardArticleID("10_WhoAmI:1VrooM21"), + IsDeleted: false, CreateTime: types.NanoTS(1607937174000000000), MTime: types.NanoTS(1607937100000000000), @@ -68,8 +72,10 @@ func TestUpdateArticleSummaries(t *testing.T) { updateNanoTS = types.NowNanoTS() - 100 articleSummary2 := &ArticleSummary{ - BBoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("1VrooM21"), + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VrooM21"), + BoardArticleID: types.BoardArticleID("10_WhoAmI:1VrooM21"), + IsDeleted: false, CreateTime: types.NanoTS(1607937174000000000), MTime: types.NanoTS(1607937100000000000), @@ -88,8 +94,10 @@ func TestUpdateArticleSummaries(t *testing.T) { updateNanoTS1 := types.NowNanoTS() articleSummary3 := &ArticleSummary{ - BBoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("1VrooM21"), + BBoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("1VrooM21"), + BoardArticleID: types.BoardArticleID("10_WhoAmI:1VrooM21"), + IsDeleted: false, CreateTime: types.NanoTS(1607937174000000000), MTime: types.NanoTS(1607937100000000000), @@ -364,8 +372,10 @@ func TestGetArticleSummariesByRegex(t *testing.T) { title1 := "有沒有這一些事情的八卦呢?~" class1 := "問題" articleSummaryWithRegex1 := &ArticleSummaryWithRegex{ - BBoardID: "10_WhoAmI", - ArticleID: "testAid0", + BBoardID: "10_WhoAmI", + ArticleID: "testAid0", + BoardArticleID: "10_WhoAmI:testAid0", + CreateTime: 1234567890000000000, MTime: 1234567890000000000, Title: title1, @@ -374,8 +384,10 @@ func TestGetArticleSummariesByRegex(t *testing.T) { UpdateNanoTS: 1234567890000000000, } articleSummary1 := &ArticleSummary{ - BBoardID: "10_WhoAmI", - ArticleID: "testAid0", + BBoardID: "10_WhoAmI", + ArticleID: "testAid0", + BoardArticleID: "10_WhoAmI:testAid0", + CreateTime: 1234567890000000000, MTime: 1234567890000000000, Title: title1, @@ -448,8 +460,10 @@ func TestGetBottomArticleSummaries(t *testing.T) { defer teardownTest() articleSummary0 := &ArticleSummaryWithRegex{ - BBoardID: "board0", - ArticleID: "1VtW-QXT", + BBoardID: "board0", + ArticleID: "1VtW-QXT", + BoardArticleID: "board0:1VtW-QXT", + CreateTime: 1608388506000000000, MTime: 1608388508000000000, Recommend: 12, @@ -465,8 +479,10 @@ func TestGetBottomArticleSummaries(t *testing.T) { } expected0 := &ArticleSummary{ - BBoardID: "board0", - ArticleID: "1VtW-QXT", + BBoardID: "board0", + ArticleID: "1VtW-QXT", + BoardArticleID: "board0:1VtW-QXT", + CreateTime: 1608388506000000000, MTime: 1608388508000000000, Recommend: 12, @@ -520,8 +536,10 @@ func TestGetArticleSummaries(t *testing.T) { defer teardownTest() articleSummary0 := &ArticleSummaryWithRegex{ - BBoardID: "board0", - ArticleID: "1VtW-QXT", + BBoardID: "board0", + ArticleID: "1VtW-QXT", + BoardArticleID: "board0:1VtW-QXT", + CreateTime: 1608388506000000000, MTime: 1608388508000000000, Recommend: 12, @@ -537,8 +555,10 @@ func TestGetArticleSummaries(t *testing.T) { } expected0 := &ArticleSummary{ - BBoardID: "board0", - ArticleID: "1VtW-QXT", + BBoardID: "board0", + ArticleID: "1VtW-QXT", + BoardArticleID: "board0:1VtW-QXT", + CreateTime: 1608388506000000000, MTime: 1608388508000000000, Recommend: 12, diff --git a/schema/article_summary_with_regex.go b/schema/article_summary_with_regex.go index e0c5f779..92c714e7 100644 --- a/schema/article_summary_with_regex.go +++ b/schema/article_summary_with_regex.go @@ -15,11 +15,13 @@ import ( // ArticleSummaryWithRegex type ArticleSummaryWithRegex struct { // ArticleSummary - BBoardID bbs.BBoardID `bson:"bid"` - ArticleID bbs.ArticleID `bson:"aid"` - IsDeleted bool `bson:"deleted,omitempty"` - CreateTime types.NanoTS `bson:"create_time_nano_ts"` - MTime types.NanoTS `bson:"mtime_nano_ts"` + BBoardID bbs.BBoardID `bson:"bid"` + ArticleID bbs.ArticleID `bson:"aid"` + BoardArticleID types.BoardArticleID `bson:"baid"` + + IsDeleted bool `bson:"deleted,omitempty"` + CreateTime types.NanoTS `bson:"create_time_nano_ts"` + MTime types.NanoTS `bson:"mtime_nano_ts"` Recommend int `bson:"recommend"` Owner bbs.UUserID `bson:"owner"` @@ -72,8 +74,10 @@ func NewArticleSummaryWithRegexFromPBArticle(boardID bbs.BBoardID, a_b *boardd.P titleRegex := articleTitleToTitleRegex(title) return &ArticleSummaryWithRegex{ - BBoardID: boardID, - ArticleID: articleID, + BBoardID: boardID, + ArticleID: articleID, + BoardArticleID: types.ToBoardArticleID(boardID, articleID), + IsDeleted: false, CreateTime: types.Time4ToNanoTS(createTime), MTime: types.NanoTS(a_b.ModifiedNsec), @@ -186,8 +190,10 @@ func NewArticleSummaryWithRegex(a_b *bbs.ArticleSummary, updateNanoTS types.Nano titleRegex := articleTitleToTitleRegex(title) return &ArticleSummaryWithRegex{ - BBoardID: a_b.BBoardID, - ArticleID: a_b.ArticleID, + BBoardID: a_b.BBoardID, + ArticleID: a_b.ArticleID, + BoardArticleID: types.ToBoardArticleID(a_b.BBoardID, a_b.ArticleID), + IsDeleted: a_b.IsDeleted, CreateTime: types.Time4ToNanoTS(a_b.CreateTime), MTime: types.Time4ToNanoTS(a_b.MTime), @@ -249,9 +255,10 @@ func UpdateArticleSummaryWithRegexes(articleSummaryWithRegexes []*ArticleSummary // create items which do not exists yet. theList := make([]*db.UpdatePair, len(articleSummaryWithRegexes)) for idx, each := range articleSummaryWithRegexes { - query := &ArticleQuery{ - BBoardID: each.BBoardID, - ArticleID: each.ArticleID, + query := &ArticleCreateQuery{ + BBoardID: each.BBoardID, + ArticleID: each.ArticleID, + BoardArticleID: types.ToBoardArticleID(each.BBoardID, each.ArticleID), } theList[idx] = &db.UpdatePair{ @@ -276,7 +283,7 @@ func UpdateArticleSummaryWithRegexes(articleSummaryWithRegexes []*ArticleSummary continue } - origFilter := each.Filter.(*ArticleQuery) + origFilter := each.Filter.(*ArticleCreateQuery) filter := bson.M{ "$or": bson.A{ bson.M{ diff --git a/schema/article_summary_with_regex_test.go b/schema/article_summary_with_regex_test.go index e8a8a749..d980c9c0 100644 --- a/schema/article_summary_with_regex_test.go +++ b/schema/article_summary_with_regex_test.go @@ -303,8 +303,10 @@ func TestNewArticleSummaryWithRegexFromPBArticle(t *testing.T) { Owner: "SYSOP", } expected0 := &ArticleSummaryWithRegex{ - BBoardID: "board0", - ArticleID: "1VtW-QXT", + BBoardID: "board0", + ArticleID: "1VtW-QXT", + BoardArticleID: "board0:1VtW-QXT", + CreateTime: 1608388506000000000, MTime: 1608388508000000000, Recommend: 12, diff --git a/schema/board.go b/schema/board.go index a76f55ed..cbaf39ad 100644 --- a/schema/board.go +++ b/schema/board.go @@ -30,7 +30,7 @@ type Board struct { VoteLimitBadpost int `bson:"vote_limit_bad_post"` PostLimitBadpost int `bson:"post_limit_bad_post"` - Parent bbs.BBoardID `bson:"parent"` + ParentID bbs.BBoardID `bson:"parent"` NVote int `bson:"vote"` /* use db-count to get current #vote */ VoteClosingTime types.NanoTS `bson:"vtime_nano_ts"` @@ -119,6 +119,10 @@ func assertBoardFields() error { return err } + if err := assertFields(EMPTY_BOARD, EMPTY_BOARD_PERM_INFO); err != nil { + return err + } + return nil } diff --git a/schema/board_detail.go b/schema/board_detail.go index 0cec45a5..abe326da 100644 --- a/schema/board_detail.go +++ b/schema/board_detail.go @@ -32,7 +32,7 @@ type BoardDetail struct { VoteLimitBadpost int `bson:"vote_limit_bad_post"` PostLimitBadpost int `bson:"post_limit_bad_post"` - Parent bbs.BBoardID `bson:"parent"` + ParentID bbs.BBoardID `bson:"parent"` NVote int `bson:"vote"` /* use db-count to get current #vote */ VoteClosingTime types.NanoTS `bson:"vtime_nano_ts"` @@ -77,6 +77,13 @@ func NewBoardDetail(b_b *bbs.BoardDetail, updateNanoTS types.NanoTS) *BoardDetai postType = DEFAULT_POST_TYPE } + var parentID bbs.BBoardID + + // XXX hack for Gid == 1 (1 should be SYSOP) + if b_b.Gid != 1 { + parentID, _ = GetBoardIDByBid(b_b.Gid) + } + return &BoardDetail{ BBoardID: b_b.BBoardID, Brdname: b_b.Brdname, @@ -110,6 +117,7 @@ func NewBoardDetail(b_b *bbs.BoardDetail, updateNanoTS types.NanoTS) *BoardDetai UpdateNanoTS: updateNanoTS, + ParentID: parentID, Gid: b_b.Gid, Bid: b_b.Bid, IdxByName: b_b.IdxByName, diff --git a/schema/board_perm_info.go b/schema/board_perm_info.go new file mode 100644 index 00000000..0128f63d --- /dev/null +++ b/schema/board_perm_info.go @@ -0,0 +1,53 @@ +package schema + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbs/ptttype" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +// BoardPermInfo +// +// Information related to Board permission +type BoardPermInfo struct { + BBoardID bbs.BBoardID `bson:"bid"` + + Brdname string `bson:"brdname"` + + BrdAttr ptttype.BrdAttr `bson:"flag"` + + BMs []bbs.UUserID `bson:"bms"` + + Level ptttype.PERM `bson:"perm"` + + ParentID bbs.BBoardID `bson:"parent"` + + VoteLimitLogins int `bson:"vote_limit_logins"` + VoteLimitBadpost int `bson:"vote_limit_bad_post"` + + PostLimitLogins int `bson:"post_limit_logins"` + PostLimitBadpost int `bson:"post_limit_bad_post"` + + NUser int `bson:"nuser"` /* use aggregate to periodically get the data */ +} + +var ( + EMPTY_BOARD_PERM_INFO = &BoardPermInfo{} + boardPermInfoFields = getFields(EMPTY_BOARD, EMPTY_BOARD_PERM_INFO) +) + +func GetBoardPermInfo(boardID bbs.BBoardID) (boardPermInfo *BoardPermInfo, err error) { + query := bson.M{ + BOARD_BBOARD_ID_b: boardID, + } + + err = Board_c.FindOne(query, &boardPermInfo, boardPermInfoFields) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + return boardPermInfo, nil +} diff --git a/schema/board_summary.go b/schema/board_summary.go index 2cb133e0..593a2686 100644 --- a/schema/board_summary.go +++ b/schema/board_summary.go @@ -30,6 +30,8 @@ type BoardSummary struct { UpdateNanoTS types.NanoTS `bson:"update_nano_ts"` + ParentID bbs.BBoardID `bson:"parent"` + Gid ptttype.Bid `bson:"pttgid"` Bid ptttype.Bid `bson:"pttbid"` IdxByName string `bson:"pttidxname"` @@ -42,6 +44,8 @@ var ( ) func NewBoardSummary(b_b *bbs.BoardSummary, updateNanoTS types.NanoTS) *BoardSummary { + parentID, _ := GetBoardIDByBid(b_b.Bid) + return &BoardSummary{ BBoardID: b_b.BBoardID, Brdname: b_b.Brdname, @@ -57,6 +61,8 @@ func NewBoardSummary(b_b *bbs.BoardSummary, updateNanoTS types.NanoTS) *BoardSum UpdateNanoTS: updateNanoTS, + ParentID: parentID, + Gid: b_b.Gid, Bid: b_b.Bid, IdxByName: b_b.IdxByName, diff --git a/schema/errors.go b/schema/errors.go index d2b84494..2489cc46 100644 --- a/schema/errors.go +++ b/schema/errors.go @@ -9,4 +9,6 @@ var ( ErrNoCreate = errors.New("no create") ErrInvalidUserFavorites = errors.New("invalid user favorites") Err2FAAlreadyExists = errors.New("2fa already exists") + + ErrNotFound = errors.New("not found") ) diff --git a/schema/init.go b/schema/init.go index ef2f5ac2..02af990f 100644 --- a/schema/init.go +++ b/schema/init.go @@ -264,21 +264,21 @@ func Init() (err error) { UserFriend_c = client.Collection("user_friend") // UserReadArticle - UserReadArticle_c = client.Collection("user_read_article") + UserArticle_c = client.Collection("user_read_article") keys = &bson.D{ - {Key: USER_READ_ARTICLE_USER_ID_b, Value: 1}, + {Key: USER_ARTICLE_USER_ID_b, Value: 1}, } - err = UserReadArticle_c.CreateIndex(keys, nil) + err = UserArticle_c.CreateIndex(keys, nil) if err != nil { return err } // UserReadBoard - UserReadBoard_c = client.Collection("user_read_board") + UserBoard_c = client.Collection("user_read_board") keys = &bson.D{ - {Key: USER_READ_BOARD_USER_ID_b, Value: 1}, + {Key: USER_BOARD_USER_ID_b, Value: 1}, } - err = UserReadBoard_c.CreateIndex(keys, nil) + err = UserBoard_c.CreateIndex(keys, nil) if err != nil { return err } @@ -289,7 +289,7 @@ func Init() (err error) { // UserVisit UserVisit_c = client.Collection("user_visit") keys = &bson.D{ - {Key: USER_READ_BOARD_USER_ID_b, Value: 1}, + {Key: USER_BOARD_USER_ID_b, Value: 1}, } err = UserVisit_c.CreateIndex(keys, nil) if err != nil { @@ -428,11 +428,11 @@ func assertAllFields() error { return err } - if err := assertUserReadArticleFields(); err != nil { + if err := assertUserArticleFields(); err != nil { return err } - if err := assertUserReadBoardFields(); err != nil { + if err := assertUserBoardFields(); err != nil { return err } diff --git a/schema/man_article_summary.go b/schema/man_article_summary.go index 5efc7bdc..cedeae81 100644 --- a/schema/man_article_summary.go +++ b/schema/man_article_summary.go @@ -5,7 +5,6 @@ import ( "github.com/Ptt-official-app/go-pttbbsweb/db" "github.com/Ptt-official-app/go-pttbbsweb/mand" "github.com/Ptt-official-app/go-pttbbsweb/types" - "github.com/sirupsen/logrus" "go.mongodb.org/mongo-driver/bson" ) @@ -76,7 +75,6 @@ func UpdateManArticleSummaries(articleSummaries []*ManArticleSummary, updateNano if err != nil { return err } - logrus.Infof("UpdateManArticleSummaries: after BulkCreateaOnly: count: %v, theList: %v", r.UpsertedCount, theList) if r.UpsertedCount == int64(len(articleSummaries)) { // all are created return nil } diff --git a/schema/testinit.go b/schema/testinit.go index d394ba5e..81397195 100644 --- a/schema/testinit.go +++ b/schema/testinit.go @@ -50,8 +50,8 @@ func testResetDB() { _ = UserFavorites_c.Drop() _ = UserFavoritesMeta_c.Drop() _ = UserFriend_c.Drop() - _ = UserReadArticle_c.Drop() - _ = UserReadBoard_c.Drop() + _ = UserArticle_c.Drop() + _ = UserBoard_c.Drop() _ = UserReject_c.Drop() _ = UserIDEmail_c.Drop() _ = UserEmail_c.Drop() diff --git a/schema/user.go b/schema/user.go index 798b4465..0747e01e 100644 --- a/schema/user.go +++ b/schema/user.go @@ -81,6 +81,12 @@ type User struct { // NFriend int `bson:"n_friend"` /* 需要透過 db-count */ Avatar []byte `bson:"avatar"` AvatarNanoTS types.NanoTS `bson:"avatar_nano_ts"` + + CooldownNanoTS types.NanoTS `bson:"cooldown_nano_ts"` + CooldownUpdateNanoTS types.NanoTS `bson:"cooldown_update_nano_ts"` + + Posttime int `bson:"postitme"` + PosttimeUpdateNanoTS types.NanoTS `bson:"posttime_update_nano_ts"` } var EMPTY_USER = &User{} @@ -156,6 +162,12 @@ var ( USER_TWO_FACTOR_ENABLED_b = getBSONName(EMPTY_USER, "TwoFactorEnabled") USER_TWO_FACTOR_ENABLED_NANO_TS_b = getBSONName(EMPTY_USER, "TwoFactorEnabledNanoTS") + + USER_COOLDOWN_NANO_TS_b = getBSONName(EMPTY_USER, "CooldownNanoTS") + USER_COOLDOWN_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER, "CooldownUpdateNanoTS") + + USER_POSTTIME_b = getBSONName(EMPTY_USER, "Posttime") + USER_POSTTIME_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER, "PosttimeUpdateNanoTS") ) func assertUserFields() error { @@ -179,6 +191,14 @@ func assertUserFields() error { return err } + if err := assertFields(EMPTY_USER, EMPTY_USER_PERM_INFO); err != nil { + return err + } + + if err := assertFields(EMPTY_USER, EMPTY_USER_POSTTIME); err != nil { + return err + } + return nil } diff --git a/schema/user_article.go b/schema/user_article.go new file mode 100644 index 00000000..3d789cf6 --- /dev/null +++ b/schema/user_article.go @@ -0,0 +1,77 @@ +package schema + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbsweb/db" + "github.com/Ptt-official-app/go-pttbbsweb/types" + "go.mongodb.org/mongo-driver/bson" +) + +var UserArticle_c *db.Collection + +type UserArticle struct { + // user-文章 + + UserID bbs.UUserID `bson:"user_id"` + BoardID bbs.BBoardID `bson:"bid"` + ArticleID bbs.ArticleID `bson:"aid"` + BoardArticleID types.BoardArticleID `bson:"baid"` + ReadUpdateNanoTS types.NanoTS `bson:"update_nano_ts"` + + ArticleBlocked bool `bson:"article_blocked"` // 不看這篇文章 + ArticleBlockedReason string `bson:"article_blocked_reason"` + ArticleBlockedUpdateNanoTS types.NanoTS `bson:"article_blocked_update_nano_ts"` + + ArticleReported bool `bson:"article_reported"` // 檢舉這篇文章 + ArticleReportedReason string `bson:"article_reported_reason"` + ArticleReportedUpdateNanoTS types.NanoTS `bson:"article_reported_update_nano_ts"` +} + +var EMPTY_USER_ARTICLE = &UserArticle{} + +var ( + USER_ARTICLE_USER_ID_b = getBSONName(EMPTY_USER_ARTICLE, "UserID") + USER_ARTICLE_BOARD_ID_b = getBSONName(EMPTY_USER_ARTICLE, "BoardID") + USER_ARTICLE_ARTICLE_ID_b = getBSONName(EMPTY_USER_ARTICLE, "ArticleID") + USER_ARTICLE_BOARD_ARTICLE_ID_b = getBSONName(EMPTY_USER_ARTICLE, "BoardArticleID") + USER_ARTICLE_READ_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_ARTICLE, "ReadUpdateNanoTS") + + USER_ARTICLE_ARTICLE_BLOCKED_b = getBSONName(EMPTY_USER_ARTICLE, "ArticleBlocked") + USER_ARTICLE_ARTICLE_BLOCKED_REASON_b = getBSONName(EMPTY_USER_ARTICLE, "ArticleBlockedReason") + USER_ARTICLE_ARTICLE_BLOCKED_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_ARTICLE, "ArticleBlockedUpdateNanoTS") + + USER_ARTICLE_ARTICLE_REPORTED_b = getBSONName(EMPTY_USER_ARTICLE, "ArticleReported") + USER_ARTICLE_ARTICLE_REPORTED_REASON_b = getBSONName(EMPTY_USER_ARTICLE, "ArticleReportedReason") + USER_ARTICLE_ARTICLE_REPORTED_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_ARTICLE, "ArticleReportedUpdateNanoTS") +) + +func assertUserArticleFields() error { + if err := assertFields(EMPTY_USER_ARTICLE, EMPTY_USER_READ_ARTICLE); err != nil { + return err + } + + if err := assertFields(EMPTY_USER_ARTICLE, EMPTY_USER_READ_ARTICLE_QUERY); err != nil { + return err + } + + return nil +} + +func FindUserArticles(userID bbs.UUserID, boardID bbs.BBoardID, articleIDs []bbs.ArticleID) ([]*UserArticle, error) { + // query + query := bson.M{ + USER_ARTICLE_USER_ID_b: userID, + USER_ARTICLE_BOARD_ID_b: boardID, + USER_ARTICLE_ARTICLE_ID_b: bson.M{ + "$in": articleIDs, + }, + } + + var dbResults []*UserArticle + err := UserBoard_c.Find(query, 0, &dbResults, nil, nil) + if err != nil { + return nil, err + } + + return dbResults, nil +} diff --git a/schema/user_board.go b/schema/user_board.go new file mode 100644 index 00000000..d2198a1a --- /dev/null +++ b/schema/user_board.go @@ -0,0 +1,107 @@ +package schema + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbsweb/db" + "github.com/Ptt-official-app/go-pttbbsweb/types" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +var UserBoard_c *db.Collection + +type UserBoard struct { + // 已讀板紀錄 + + UserID bbs.UUserID `bson:"user_id"` + BBoardID bbs.BBoardID `bson:"bid"` + ReadUpdateNanoTS types.NanoTS `bson:"update_nano_ts"` + + BoardFriend bool `bson:"board_friend"` + BoardFriendUpdateNanoTS types.NanoTS `bson:"board_friend_update_nano_ts"` + + BoardBucketExpireNanoTS types.NanoTS `bson:"board_bucket_expire_nano_ts"` // 水桶 + BoardBucketReason string `bson:"board_bucket_reason"` + BoardBucketUpdateNanoTS types.NanoTS `bson:"board_bucket_update_nano_ts"` + + BoardBlocked bool `bson:"board_blocked"` // 不看這個板 + BoardBlockedReason string `bson:"board_blocked_reason"` + BoardBlockedUpdateNanoTS types.NanoTS `bson:"board_blocked_update_nano_ts"` + + BoardReported bool `bson:"board_reported"` // 檢舉這個板 + BoardReportedReason string `bson:"board_reported_reason"` + BoardReportedUpdateNanoTS types.NanoTS `bson:"board_reported_update_nano_ts"` +} + +var EMPTY_USER_BOARD = &UserBoard{} + +var ( + USER_BOARD_USER_ID_b = getBSONName(EMPTY_USER_BOARD, "UserID") + USER_BOARD_BBOARD_ID_b = getBSONName(EMPTY_USER_BOARD, "BBoardID") + USER_BOARD_READ_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_BOARD, "ReadUpdateNanoTS") + + USER_BOARD_BOARD_FRIEND_b = getBSONName(EMPTY_USER_BOARD, "BoardFriend") + USER_BOARD_BOARD_FRIEND_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_BOARD, "BoardFriendUpdateNanoTS") + + USER_BOARD_BOARD_BUCKET_b = getBSONName(EMPTY_USER_BOARD, "BoardBucket") + USER_BOARD_BOARD_BUCKET_REASON_b = getBSONName(EMPTY_USER_BOARD, "BoardBucketReason") + USER_BOARD_BOARD_BUCKET_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_BOARD, "BoardBucketUpdateNanoTS") + + USER_BOARD_BOARD_BLOCKED_b = getBSONName(EMPTY_USER_BOARD, "BoardBlocked") + USER_BOARD_BOARD_BLOCKED_REASON_b = getBSONName(EMPTY_USER_BOARD, "BoardBlockedReason") + USER_BOARD_BOARD_BLOCKED_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_BOARD, "BoardBlockedUpdateNanoTS") + + USER_BOARD_BOARD_REPORTED_b = getBSONName(EMPTY_USER_BOARD, "BoardReported") + USER_BOARD_BOARD_REPORTED_REASON_b = getBSONName(EMPTY_USER_BOARD, "BoardReportedReason") + USER_BOARD_BOARD_REPORTED_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_BOARD, "BoardReportedUpdateNanoTS") +) + +func assertUserBoardFields() error { + if err := assertFields(EMPTY_USER_BOARD, EMPTY_USER_READ_BOARD); err != nil { + return err + } + + if err := assertFields(EMPTY_USER_BOARD, EMPTY_USER_READ_BOARD_QUERY); err != nil { + return err + } + + return nil +} + +func FindUserBoard(userID bbs.UUserID, boardID bbs.BBoardID) (result *UserBoard, err error) { + // query + query := bson.M{ + USER_BOARD_USER_ID_b: userID, + USER_BOARD_BBOARD_ID_b: boardID, + } + + err = UserBoard_c.FindOne(query, &result, nil) + if err == mongo.ErrNoDocuments { + return &UserBoard{UserID: userID, BBoardID: boardID}, nil + } + if err != nil { + return nil, err + } + + return result, nil +} + +func FindUserBoards(userID bbs.UUserID, boardIDs []bbs.BBoardID) (result []*UserBoard, err error) { + // query + query := bson.M{ + USER_BOARD_USER_ID_b: userID, + USER_BOARD_BBOARD_ID_b: bson.M{ + "$in": boardIDs, + }, + } + + err = UserBoard_c.Find(query, 0, &result, nil, nil) + if err == mongo.ErrNoDocuments { + return []*UserBoard{}, nil + } + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/schema/user_board_test.go b/schema/user_board_test.go new file mode 100644 index 00000000..62190014 --- /dev/null +++ b/schema/user_board_test.go @@ -0,0 +1,95 @@ +package schema + +import ( + "reflect" + "sort" + "strings" + "testing" + + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbs/testutil" + "github.com/Ptt-official-app/go-pttbbsweb/types" +) + +func TestFindUserBoards(t *testing.T) { + setupTest() + defer teardownTest() + + defer UserBoard_c.Drop() + + userReadBoards := []*UserReadBoard{ + { + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard0"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), + }, + { + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard1"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), + }, + { + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard2"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), + }, + } + + _ = UpdateUserReadBoards(userReadBoards, types.NanoTS(1234567890000000000)) + + userBoards := []*UserBoard{ + { + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard0"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), + }, + { + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard1"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), + }, + { + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard2"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), + }, + } + + type args struct { + userID bbs.UUserID + boardIDs []bbs.BBoardID + } + tests := []struct { + name string + args args + want []*UserBoard + wantErr bool + }{ + // TODO: Add test cases. + // TODO: Add test cases. + { + args: args{ + userID: bbs.UUserID("testuser0"), + boardIDs: []bbs.BBoardID{"testboard0", "testboard1", "testboard2"}, + }, + want: userBoards, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := FindUserBoards(tt.args.userID, tt.args.boardIDs) + if (err != nil) != tt.wantErr { + t.Errorf("FindUserBoards() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FindUserBoards() = %v, want %v", got, tt.want) + } + + sort.SliceStable(got, func(i, j int) bool { + return strings.Compare(string(got[i].BBoardID), string(got[j].BBoardID)) <= 0 + }) + testutil.TDeepEqual(t, "got", got, tt.want) + }) + } +} diff --git a/schema/user_favorites_test.go b/schema/user_favorites_test.go index 2fcf2fdf..b2c3bb8c 100644 --- a/schema/user_favorites_test.go +++ b/schema/user_favorites_test.go @@ -17,7 +17,7 @@ func TestFavToUserFavorites(t *testing.T) { /* filename1 := "./testcase/home1/t/testUser2/.fav" - theBytes1, _ := ioutil.ReadFile(filename1) + theBytes1, _ := os.ReadFile(filename1) buf := bytes.NewReader(theBytes1) _ = binary.Read(buf, binary.LittleEndian, &version) f1, _ := fav.ReadFavrec(buf, nil, nil, 0) diff --git a/schema/user_info_summary.go b/schema/user_info_summary.go index 0d4bf970..2568d9c9 100644 --- a/schema/user_info_summary.go +++ b/schema/user_info_summary.go @@ -3,6 +3,7 @@ package schema import ( "github.com/Ptt-official-app/go-pttbbs/bbs" "github.com/Ptt-official-app/go-pttbbsweb/types" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) @@ -18,11 +19,10 @@ var ( ) func GetUserInfoSummary(userID bbs.UUserID) (result *UserInfoSummary, err error) { - query := &UserQuery{ - UserID: userID, + query := bson.M{ + USER_USER_ID_b: userID, } - result = &UserInfoSummary{} err = User_c.FindOne(query, &result, userInfoSummaryFields) if err == mongo.ErrNoDocuments { return nil, nil diff --git a/schema/user_new_info.go b/schema/user_new_info.go index 2cc552e6..e4285b73 100644 --- a/schema/user_new_info.go +++ b/schema/user_new_info.go @@ -3,6 +3,7 @@ package schema import ( "github.com/Ptt-official-app/go-pttbbs/bbs" "github.com/Ptt-official-app/go-pttbbsweb/types" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) @@ -19,8 +20,8 @@ var ( ) func GetUserNewInfo(userID bbs.UUserID) (result *UserNewInfo, err error) { - query := &UserQuery{ - UserID: userID, + query := bson.M{ + USER_USER_ID_b: userID, } err = User_c.FindOne(query, &result, userNewInfoFields) diff --git a/schema/user_nickname.go b/schema/user_nickname.go index 51c0c33d..0453a4c0 100644 --- a/schema/user_nickname.go +++ b/schema/user_nickname.go @@ -2,6 +2,7 @@ package schema import ( "github.com/Ptt-official-app/go-pttbbs/bbs" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" ) @@ -16,11 +17,11 @@ var ( ) func GetUserNickname(userID bbs.UUserID) (nickname string, err error) { - query := &UserQuery{ - UserID: userID, + query := bson.M{ + USER_USER_ID_b: userID, } - result := &UserNickname{} + var result *UserNickname err = User_c.FindOne(query, &result, userNicknameFields) if err == mongo.ErrNoDocuments { return "", nil diff --git a/schema/user_perm_info.go b/schema/user_perm_info.go new file mode 100644 index 00000000..013119ad --- /dev/null +++ b/schema/user_perm_info.go @@ -0,0 +1,44 @@ +package schema + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbs/ptttype" + "github.com/Ptt-official-app/go-pttbbsweb/types" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type UserPermInfo struct { + UserID bbs.UUserID `bson:"user_id"` + + Userlevel ptttype.PERM `bson:"perm"` + + Over18 bool `bson:"over18"` + + Numlogindays int `bson:"login_days"` + + BadPost int `bson:"bad_post"` /* 評價為壞文章數 */ + + CooldownNanoTS types.NanoTS `bson:"cooldown_nano_ts"` + Posttime int `bson:"postitme"` +} + +var ( + EMPTY_USER_PERM_INFO = &UserPermInfo{} + userPermInfoFields = getFields(EMPTY_USER, EMPTY_USER_PERM_INFO) +) + +func GetUserPermInfo(userID bbs.UUserID) (userPermInfo *UserPermInfo, err error) { + query := bson.M{ + USER_USER_ID_b: userID, + } + + err = User_c.FindOne(query, &userPermInfo, userPermInfoFields) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + return userPermInfo, nil +} diff --git a/schema/user_posttime.go b/schema/user_posttime.go new file mode 100644 index 00000000..68a2d20b --- /dev/null +++ b/schema/user_posttime.go @@ -0,0 +1,33 @@ +package schema + +import ( + "github.com/Ptt-official-app/go-pttbbs/bbs" + "github.com/Ptt-official-app/go-pttbbsweb/types" + "go.mongodb.org/mongo-driver/bson" +) + +type UserPosttime struct { + UserID bbs.UUserID `bson:"user_id"` + Posttime int `bson:"postitme"` + PosttimeUpdateNanoTS types.NanoTS `bson:"posttime_update_nano_ts"` +} + +var EMPTY_USER_POSTTIME = &UserPosttime{} + +func UpdateUserPosttime(userID bbs.UUserID, postTime int) (err error) { + nowNanoTS := types.NowNanoTS() + query := bson.M{ + USER_USER_ID_b: userID, + USER_POSTTIME_UPDATE_NANO_TS_b: bson.M{ + "$lt": nowNanoTS, + }, + } + update := bson.M{ + USER_POSTTIME_b: postTime, + USER_POSTTIME_UPDATE_NANO_TS_b: nowNanoTS, + } + + _, err = User_c.UpdateOneOnly(query, update) + + return err +} diff --git a/schema/user_read_article.go b/schema/user_read_article.go index 768c5377..7945099a 100644 --- a/schema/user_read_article.go +++ b/schema/user_read_article.go @@ -7,34 +7,16 @@ import ( "go.mongodb.org/mongo-driver/bson" ) -var UserReadArticle_c *db.Collection - type UserReadArticle struct { - // 已讀文章紀錄 - - UserID bbs.UUserID `bson:"user_id"` - BoardID bbs.BBoardID `bson:"bid"` - ArticleID bbs.ArticleID `bson:"aid"` - UpdateNanoTS types.NanoTS `bson:"update_nano_ts"` + UserID bbs.UUserID `bson:"user_id"` + BoardID bbs.BBoardID `bson:"bid"` + ArticleID bbs.ArticleID `bson:"aid"` + BoardArticleID types.BoardArticleID `bson:"baid"` + ReadUpdateNanoTS types.NanoTS `bson:"update_nano_ts"` } var EMPTY_USER_READ_ARTICLE = &UserReadArticle{} -var ( - USER_READ_ARTICLE_USER_ID_b = getBSONName(EMPTY_USER_READ_ARTICLE, "UserID") - USER_READ_ARTICLE_BOARD_ID_b = getBSONName(EMPTY_USER_READ_ARTICLE, "BoardID") - USER_READ_ARTICLE_ARTICLE_ID_b = getBSONName(EMPTY_USER_READ_ARTICLE, "ArticleID") - USER_READ_ARTICLE_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_READ_ARTICLE, "UpdateNanoTS") -) - -func assertUserReadArticleFields() error { - if err := assertFields(EMPTY_USER_READ_ARTICLE, EMPTY_USER_READ_ARTICLE_QUERY); err != nil { - return err - } - - return nil -} - type UserReadArticleQuery struct { UserID bbs.UUserID `bson:"user_id"` BoardID bbs.BBoardID `bson:"bid"` @@ -44,13 +26,16 @@ type UserReadArticleQuery struct { var EMPTY_USER_READ_ARTICLE_QUERY = &UserReadArticleQuery{} func UpdateUserReadArticle(userReadArticle *UserReadArticle) (err error) { + // ensure board-article-id + userReadArticle.BoardArticleID = types.ToBoardArticleID(userReadArticle.BoardID, userReadArticle.ArticleID) + query := bson.M{ - USER_READ_ARTICLE_USER_ID_b: userReadArticle.UserID, - USER_READ_ARTICLE_BOARD_ID_b: userReadArticle.BoardID, - USER_READ_ARTICLE_ARTICLE_ID_b: userReadArticle.ArticleID, + USER_ARTICLE_USER_ID_b: userReadArticle.UserID, + USER_ARTICLE_BOARD_ID_b: userReadArticle.BoardID, + USER_ARTICLE_ARTICLE_ID_b: userReadArticle.ArticleID, } - r, err := UserReadArticle_c.CreateOnly(query, userReadArticle) + r, err := UserArticle_c.CreateOnly(query, userReadArticle) if err != nil { return err } @@ -58,11 +43,11 @@ func UpdateUserReadArticle(userReadArticle *UserReadArticle) (err error) { return nil } - query[USER_READ_ARTICLE_UPDATE_NANO_TS_b] = bson.M{ - "$lt": userReadArticle.UpdateNanoTS, + query[USER_ARTICLE_READ_UPDATE_NANO_TS_b] = bson.M{ + "$lt": userReadArticle.ReadUpdateNanoTS, } - _, err = UserReadArticle_c.UpdateOneOnly(query, userReadArticle) + _, err = UserArticle_c.UpdateOneOnly(query, userReadArticle) return err } @@ -72,6 +57,11 @@ func UpdateUserReadArticles(userReadArticles []*UserReadArticle, updateNanoTS ty return nil } + // ensure board-article-id + for _, each := range userReadArticles { + each.BoardArticleID = types.ToBoardArticleID(each.BoardID, each.ArticleID) + } + theList := make([]*db.UpdatePair, len(userReadArticles)) for idx, each := range userReadArticles { query := &UserReadArticleQuery{ @@ -86,7 +76,7 @@ func UpdateUserReadArticles(userReadArticles []*UserReadArticle, updateNanoTS ty } } - r, err := UserReadArticle_c.BulkCreateOnly(theList) + r, err := UserArticle_c.BulkCreateOnly(theList) if err != nil { return err } @@ -104,9 +94,9 @@ func UpdateUserReadArticles(userReadArticles []*UserReadArticle, updateNanoTS ty origFilter := each.Filter.(*UserReadArticleQuery) filter := bson.M{ - USER_READ_ARTICLE_USER_ID_b: origFilter.UserID, - USER_READ_ARTICLE_ARTICLE_ID_b: origFilter.ArticleID, - USER_READ_ARTICLE_UPDATE_NANO_TS_b: bson.M{ + USER_ARTICLE_USER_ID_b: origFilter.UserID, + USER_ARTICLE_ARTICLE_ID_b: origFilter.ArticleID, + USER_ARTICLE_READ_UPDATE_NANO_TS_b: bson.M{ "$lt": updateNanoTS, }, } @@ -114,7 +104,7 @@ func UpdateUserReadArticles(userReadArticles []*UserReadArticle, updateNanoTS ty updateUserReadArticles = append(updateUserReadArticles, each) } - _, err = UserReadArticle_c.BulkUpdateOneOnly(updateUserReadArticles) + _, err = UserArticle_c.BulkUpdateOneOnly(updateUserReadArticles) return err } @@ -122,15 +112,15 @@ func UpdateUserReadArticles(userReadArticles []*UserReadArticle, updateNanoTS ty func FindUserReadArticles(userID bbs.UUserID, boardID bbs.BBoardID, articleIDs []bbs.ArticleID) ([]*UserReadArticle, error) { // query query := bson.M{ - USER_READ_ARTICLE_USER_ID_b: userID, - USER_READ_ARTICLE_BOARD_ID_b: boardID, - USER_READ_ARTICLE_ARTICLE_ID_b: bson.M{ + USER_ARTICLE_USER_ID_b: userID, + USER_ARTICLE_BOARD_ID_b: boardID, + USER_ARTICLE_ARTICLE_ID_b: bson.M{ "$in": articleIDs, }, } var dbResults []*UserReadArticle - err := UserReadArticle_c.Find(query, 0, &dbResults, nil, nil) + err := UserArticle_c.Find(query, 0, &dbResults, nil, nil) if err != nil { return nil, err } @@ -138,17 +128,18 @@ func FindUserReadArticles(userID bbs.UUserID, boardID bbs.BBoardID, articleIDs [ return dbResults, nil } -func FindUserReadArticlesByArticleIDs(userID bbs.UUserID, articleIDs []bbs.ArticleID) ([]*UserReadArticle, error) { +func FindUserReadArticlesByBoardArticleIDs(userID bbs.UUserID, boardArticleIDs []types.BoardArticleID) ([]*UserReadArticle, error) { // query + query := bson.M{ - USER_READ_ARTICLE_USER_ID_b: userID, - USER_READ_ARTICLE_ARTICLE_ID_b: bson.M{ - "$in": articleIDs, + USER_ARTICLE_USER_ID_b: userID, + USER_ARTICLE_BOARD_ARTICLE_ID_b: bson.M{ + "$in": boardArticleIDs, }, } var dbResults []*UserReadArticle - err := UserReadArticle_c.Find(query, 0, &dbResults, nil, nil) + err := UserArticle_c.Find(query, 0, &dbResults, nil, nil) if err != nil { return nil, err } diff --git a/schema/user_read_article_delete.go b/schema/user_read_article_delete.go deleted file mode 100644 index a979f7c7..00000000 --- a/schema/user_read_article_delete.go +++ /dev/null @@ -1,27 +0,0 @@ -package schema - -import ( - "github.com/Ptt-official-app/go-pttbbs/bbs" - "github.com/Ptt-official-app/go-pttbbsweb/types" - "go.mongodb.org/mongo-driver/bson" -) - -type UserReadArticleIsDeleted struct { - IsDeleted bool `bson:"deleted,omitempty"` - UpdateNanoTS types.NanoTS `bson:"update_nano_ts"` -} - -// DeleteUserReadArticles deletes user_read_articles in Database -func DeleteUserReadArticles(boardID bbs.BBoardID, articleIDs []bbs.ArticleID, updateNanoTS types.NanoTS) (err error) { - query := bson.M{ - ARTICLE_BBOARD_ID_b: boardID, - ARTICLE_ARTICLE_ID_b: bson.M{"$in": articleIDs}, - ARTICLE_UPDATE_NANO_TS_b: bson.M{"$lt": updateNanoTS}, - } - update := &UserReadArticleIsDeleted{ - IsDeleted: true, - UpdateNanoTS: updateNanoTS, - } - _, err = UserReadArticle_c.UpdateManyOnly(query, update) - return err -} diff --git a/schema/user_read_article_delete_test.go b/schema/user_read_article_delete_test.go deleted file mode 100644 index 951bea42..00000000 --- a/schema/user_read_article_delete_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package schema - -import ( - "testing" - - "github.com/Ptt-official-app/go-pttbbs/bbs" - "github.com/Ptt-official-app/go-pttbbsweb/types" -) - -func TestDeleteUserReadArticles(t *testing.T) { - setupTest() - defer teardownTest() - defer UserReadArticle_c.Drop() - - userReadArticles := []*UserReadArticle{ - { - UserID: bbs.UUserID("testuser0"), - BoardID: bbs.BBoardID("test"), - ArticleID: bbs.ArticleID("test"), - UpdateNanoTS: types.NanoTS(1234567890000000000), - }, - } - - _ = UpdateUserReadArticles(userReadArticles, types.NanoTS(1234567890000000000)) - type args struct { - boardID bbs.BBoardID - articleIDs []bbs.ArticleID - updateNanoTS types.NanoTS - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "test deleting user_read_articles", - args: args{ - bbs.BBoardID("test"), - []bbs.ArticleID{bbs.ArticleID("test")}, - types.NowNanoTS(), - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := DeleteUserReadArticles(tt.args.boardID, tt.args.articleIDs, tt.args.updateNanoTS); (err != nil) != tt.wantErr { - t.Errorf("DeleteUserReadArticles() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/schema/user_read_article_test.go b/schema/user_read_article_test.go index c88600c4..fbd51d15 100644 --- a/schema/user_read_article_test.go +++ b/schema/user_read_article_test.go @@ -15,26 +15,26 @@ func TestUpdateUserReadArticles(t *testing.T) { setupTest() defer teardownTest() - defer UserReadArticle_c.Drop() + defer UserArticle_c.Drop() userReadArticles := []*UserReadArticle{ { - UserID: bbs.UUserID("testuser0"), - BoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("testarticle0"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("testarticle0"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, { - UserID: bbs.UUserID("testuser0"), - BoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("testarticle1"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("testarticle1"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, { - UserID: bbs.UUserID("testuser0"), - BoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("testarticle2"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("testarticle2"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, } @@ -49,7 +49,7 @@ func TestUpdateUserReadArticles(t *testing.T) { findUserID bbs.UUserID findBoardID bbs.BBoardID findArticleIDs []bbs.ArticleID - expected []*UserReadArticle + want []*UserReadArticle }{ // TODO: Add test cases. { @@ -57,14 +57,14 @@ func TestUpdateUserReadArticles(t *testing.T) { findUserID: bbs.UUserID("testuser0"), findBoardID: bbs.BBoardID("10_WhoAmI"), findArticleIDs: []bbs.ArticleID{"testarticle0", "testarticle1", "testarticle2"}, - expected: userReadArticles, + want: userReadArticles, }, { args: args{userReadArticles: userReadArticles, updateNanoTS: types.NanoTS(1234567890000000000)}, findUserID: bbs.UUserID("testuser0"), findBoardID: bbs.BBoardID("10_WhoAmI"), findArticleIDs: []bbs.ArticleID{"testarticle0", "testarticle1", "testarticle2"}, - expected: userReadArticles, + want: userReadArticles, }, } for _, tt := range tests { @@ -81,7 +81,7 @@ func TestUpdateUserReadArticles(t *testing.T) { sort.SliceStable(got, func(i, j int) bool { return strings.Compare(string(got[i].ArticleID), string(got[j].ArticleID)) <= 0 }) - testutil.TDeepEqual(t, "got", got, tt.expected) + testutil.TDeepEqual(t, "got", got, tt.want) }) } } @@ -90,13 +90,13 @@ func TestUpdateUserReadArticle(t *testing.T) { setupTest() defer teardownTest() - defer UserReadArticle_c.Drop() + defer UserArticle_c.Drop() userReadArticle := &UserReadArticle{ - UserID: bbs.UUserID("testuser0"), - BoardID: bbs.BBoardID("10_WhoAmI"), - ArticleID: bbs.ArticleID("testarticle0"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BoardID: bbs.BBoardID("10_WhoAmI"), + ArticleID: bbs.ArticleID("testarticle0"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), } type args struct { userReadArticle *UserReadArticle @@ -126,11 +126,11 @@ func TestUpdateUserReadArticle(t *testing.T) { got := &UserReadArticle{} query := bson.M{ - USER_READ_ARTICLE_USER_ID_b: tt.args.userReadArticle.UserID, - USER_READ_ARTICLE_BOARD_ID_b: tt.args.userReadArticle.BoardID, - USER_READ_ARTICLE_ARTICLE_ID_b: tt.args.userReadArticle.ArticleID, + USER_ARTICLE_USER_ID_b: tt.args.userReadArticle.UserID, + USER_ARTICLE_BOARD_ID_b: tt.args.userReadArticle.BoardID, + USER_ARTICLE_ARTICLE_ID_b: tt.args.userReadArticle.ArticleID, } - _ = UserReadArticle_c.FindOne(query, got, nil) + _ = UserArticle_c.FindOne(query, got, nil) testutil.TDeepEqual(t, "got", got, tt.expected) }) diff --git a/schema/user_read_board.go b/schema/user_read_board.go index cb8c90ad..1540c6fd 100644 --- a/schema/user_read_board.go +++ b/schema/user_read_board.go @@ -7,46 +7,32 @@ import ( "go.mongodb.org/mongo-driver/bson" ) -var UserReadBoard_c *db.Collection - type UserReadBoard struct { - // 已讀板紀錄 - - UserID bbs.UUserID `bson:"user_id"` - BBoardID bbs.BBoardID `bson:"bid"` - UpdateNanoTS types.NanoTS `bson:"update_nano_ts"` + UserID bbs.UUserID `bson:"user_id"` + BBoardID bbs.BBoardID `bson:"bid"` + ReadUpdateNanoTS types.NanoTS `bson:"update_nano_ts"` } var EMPTY_USER_READ_BOARD = &UserReadBoard{} -var ( - USER_READ_BOARD_USER_ID_b = getBSONName(EMPTY_USER_READ_BOARD, "UserID") - USER_READ_BOARD_BBOARD_ID_b = getBSONName(EMPTY_USER_READ_BOARD, "BBoardID") - USER_READ_BOARD_UPDATE_NANO_TS_b = getBSONName(EMPTY_USER_READ_BOARD, "UpdateNanoTS") -) - -func assertUserReadBoardFields() error { - if err := assertFields(EMPTY_USER_READ_BOARD, EMPTY_USER_READ_BOARD_QUERY); err != nil { - return err - } - - return nil -} - type UserReadBoardQuery struct { UserID bbs.UUserID `bson:"user_id"` BBoardID bbs.BBoardID `bson:"bid"` } -var EMPTY_USER_READ_BOARD_QUERY = &UserReadBoardQuery{} +var ( + EMPTY_USER_READ_BOARD_QUERY = &UserReadBoardQuery{} + + userReadBoardFields = getFields(EMPTY_USER_BOARD, EMPTY_USER_READ_BOARD) +) func UpdateUserReadBoard(userReadBoard *UserReadBoard) (err error) { query := bson.M{ - USER_READ_BOARD_USER_ID_b: userReadBoard.UserID, - USER_READ_BOARD_BBOARD_ID_b: userReadBoard.BBoardID, + USER_BOARD_USER_ID_b: userReadBoard.UserID, + USER_BOARD_BBOARD_ID_b: userReadBoard.BBoardID, } - r, err := UserReadBoard_c.CreateOnly(query, userReadBoard) + r, err := UserBoard_c.CreateOnly(query, userReadBoard) if err != nil { return err } @@ -54,11 +40,11 @@ func UpdateUserReadBoard(userReadBoard *UserReadBoard) (err error) { return nil } - query[USER_READ_BOARD_UPDATE_NANO_TS_b] = bson.M{ - "$lt": userReadBoard.UpdateNanoTS, + query[USER_BOARD_READ_UPDATE_NANO_TS_b] = bson.M{ + "$lt": userReadBoard.ReadUpdateNanoTS, } - _, err = UserReadBoard_c.UpdateOneOnly(query, userReadBoard) + _, err = UserBoard_c.UpdateOneOnly(query, userReadBoard) return err } @@ -78,7 +64,7 @@ func UpdateUserReadBoards(userReadBoards []*UserReadBoard, updateNanoTS types.Na } } - r, err := UserReadBoard_c.BulkCreateOnly(theList) + r, err := UserBoard_c.BulkCreateOnly(theList) if err != nil { return err } @@ -96,9 +82,9 @@ func UpdateUserReadBoards(userReadBoards []*UserReadBoard, updateNanoTS types.Na origFilter := each.Filter.(*UserReadBoardQuery) filter := bson.M{ - USER_READ_BOARD_USER_ID_b: origFilter.UserID, - USER_READ_BOARD_BBOARD_ID_b: origFilter.BBoardID, - USER_READ_BOARD_UPDATE_NANO_TS_b: bson.M{ + USER_BOARD_USER_ID_b: origFilter.UserID, + USER_BOARD_BBOARD_ID_b: origFilter.BBoardID, + USER_BOARD_READ_UPDATE_NANO_TS_b: bson.M{ "$lt": updateNanoTS, }, } @@ -106,7 +92,7 @@ func UpdateUserReadBoards(userReadBoards []*UserReadBoard, updateNanoTS types.Na updateUserReadBoards = append(updateUserReadBoards, each) } - _, err = UserReadBoard_c.BulkUpdateOneOnly(updateUserReadBoards) + _, err = UserBoard_c.BulkUpdateOneOnly(updateUserReadBoards) return err } @@ -114,14 +100,14 @@ func UpdateUserReadBoards(userReadBoards []*UserReadBoard, updateNanoTS types.Na func FindUserReadBoards(userID bbs.UUserID, boardIDs []bbs.BBoardID) ([]*UserReadBoard, error) { // query query := bson.M{ - USER_READ_BOARD_USER_ID_b: userID, - USER_READ_BOARD_BBOARD_ID_b: bson.M{ + USER_BOARD_USER_ID_b: userID, + USER_BOARD_BBOARD_ID_b: bson.M{ "$in": boardIDs, }, } var dbResults []*UserReadBoard - err := UserReadBoard_c.Find(query, 0, &dbResults, nil, nil) + err := UserBoard_c.Find(query, 0, &dbResults, userReadBoardFields, nil) if err != nil { return nil, err } diff --git a/schema/user_read_board_test.go b/schema/user_read_board_test.go index ec4e1934..8ed88629 100644 --- a/schema/user_read_board_test.go +++ b/schema/user_read_board_test.go @@ -16,31 +16,31 @@ func TestUpdateUserReadBoard(t *testing.T) { setupTest() defer teardownTest() - defer UserReadBoard_c.Drop() + defer UserBoard_c.Drop() userReadBoard := &UserReadBoard{ - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard0"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard0"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), } type args struct { userReadBoard *UserReadBoard } tests := []struct { - name string - args args - expected *UserReadBoard - wantErr bool + name string + args args + want *UserReadBoard + wantErr bool }{ // TODO: Add test cases. { - args: args{userReadBoard}, - expected: userReadBoard, + args: args{userReadBoard}, + want: userReadBoard, }, { - args: args{userReadBoard}, - expected: userReadBoard, + args: args{userReadBoard}, + want: userReadBoard, }, } for _, tt := range tests { @@ -52,12 +52,12 @@ func TestUpdateUserReadBoard(t *testing.T) { got := &UserReadBoard{} query := bson.M{ - USER_READ_BOARD_USER_ID_b: tt.args.userReadBoard.UserID, - USER_READ_BOARD_BBOARD_ID_b: tt.args.userReadBoard.BBoardID, + USER_BOARD_USER_ID_b: tt.args.userReadBoard.UserID, + USER_BOARD_BBOARD_ID_b: tt.args.userReadBoard.BBoardID, } - _ = UserReadBoard_c.FindOne(query, got, nil) - testutil.TDeepEqual(t, "got", got, tt.expected) + _ = UserBoard_c.FindOne(query, got, nil) + testutil.TDeepEqual(t, "got", got, tt.want) }) } } @@ -66,35 +66,35 @@ func TestUpdateUserReadBoards(t *testing.T) { setupTest() defer teardownTest() - defer UserReadBoard_c.Drop() + defer UserBoard_c.Drop() userReadBoards := []*UserReadBoard{ { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard0"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard0"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard1"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard1"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard2"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard2"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, } userReadBoards1 := []*UserReadBoard{ { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard2"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard2"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard3"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard3"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, } @@ -107,7 +107,7 @@ func TestUpdateUserReadBoards(t *testing.T) { args args findUserID bbs.UUserID findBoardIDs []bbs.BBoardID - expected []*UserReadBoard + wnat []*UserReadBoard wantErr bool }{ // TODO: Add test cases. @@ -115,19 +115,19 @@ func TestUpdateUserReadBoards(t *testing.T) { args: args{userReadBoards: userReadBoards, updateNanoTS: types.NanoTS(1234567890000000000)}, findUserID: bbs.UUserID("testuser0"), findBoardIDs: []bbs.BBoardID{"testboard0", "testboard1", "testboard2"}, - expected: userReadBoards, + wnat: userReadBoards, }, { args: args{userReadBoards: userReadBoards, updateNanoTS: types.NanoTS(1234567890000000000)}, findUserID: bbs.UUserID("testuser0"), findBoardIDs: []bbs.BBoardID{"testboard0", "testboard1", "testboard2"}, - expected: userReadBoards, + wnat: userReadBoards, }, { args: args{userReadBoards: userReadBoards1, updateNanoTS: types.NanoTS(1234567890000000000)}, findUserID: bbs.UUserID("testuser0"), findBoardIDs: []bbs.BBoardID{"testboard2", "testboard3"}, - expected: userReadBoards1, + wnat: userReadBoards1, }, } for _, tt := range tests { @@ -142,7 +142,7 @@ func TestUpdateUserReadBoards(t *testing.T) { sort.SliceStable(got, func(i, j int) bool { return strings.Compare(string(got[i].BBoardID), string(got[j].BBoardID)) <= 0 }) - testutil.TDeepEqual(t, "got", got, tt.expected) + testutil.TDeepEqual(t, "got", got, tt.wnat) t.Errorf("false") @@ -155,23 +155,23 @@ func TestFindUserReadBoards(t *testing.T) { setupTest() defer teardownTest() - defer UserReadBoard_c.Drop() + defer UserBoard_c.Drop() userReadBoards := []*UserReadBoard{ { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard0"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard0"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard1"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard1"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, { - UserID: bbs.UUserID("testuser0"), - BBoardID: bbs.BBoardID("testboard2"), - UpdateNanoTS: types.NanoTS(1234567890000000000), + UserID: bbs.UUserID("testuser0"), + BBoardID: bbs.BBoardID("testboard2"), + ReadUpdateNanoTS: types.NanoTS(1234567890000000000), }, } @@ -182,10 +182,10 @@ func TestFindUserReadBoards(t *testing.T) { boardIDs []bbs.BBoardID } tests := []struct { - name string - args args - expected []*UserReadBoard - wantErr bool + name string + args args + want []*UserReadBoard + wantErr bool }{ // TODO: Add test cases. { @@ -193,7 +193,7 @@ func TestFindUserReadBoards(t *testing.T) { userID: bbs.UUserID("testuser0"), boardIDs: []bbs.BBoardID{"testboard0", "testboard1", "testboard2"}, }, - expected: userReadBoards, + want: userReadBoards, }, } for _, tt := range tests { @@ -203,14 +203,14 @@ func TestFindUserReadBoards(t *testing.T) { t.Errorf("FindUserReadBoards() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.expected) { - t.Errorf("FindUserReadBoards() = %v, want %v", got, tt.expected) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FindUserReadBoards() = %v, want %v", got, tt.want) } sort.SliceStable(got, func(i, j int) bool { return strings.Compare(string(got[i].BBoardID), string(got[j].BBoardID)) <= 0 }) - testutil.TDeepEqual(t, "got", got, tt.expected) + testutil.TDeepEqual(t, "got", got, tt.want) }) } } diff --git a/types/const.go b/types/const.go index e099b51c..25016617 100644 --- a/types/const.go +++ b/types/const.go @@ -15,4 +15,6 @@ var ( COLOR_RESET_BYTES = []byte("\x1b[m") DEFAULT_LEN_COLOR_BYTES = 20 //\x1b[0;1;5;37;40m + + POSTTIME_REJECT = 15 ) diff --git a/types/types.go b/types/types.go index 62777c84..30706af7 100644 --- a/types/types.go +++ b/types/types.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/Ptt-official-app/go-pttbbs/bbs" pttbbstypes "github.com/Ptt-official-app/go-pttbbs/types" "github.com/sirupsen/logrus" ) @@ -59,6 +60,9 @@ func (m ManArticleID) ToCreateTime() (createTime NanoTS) { } // ContentID +// +// ContentID is always queried / updated with ArticleID, ManArticleID. +// No need to add boardID and articleID inside ContentID. type ContentID string func ToContentID(nanoTS NanoTS, md5 string) ContentID { @@ -67,6 +71,8 @@ func ToContentID(nanoTS NanoTS, md5 string) ContentID { // CommentID // +// owner-id is embedded in md5 +// // XXX currently it's very hard to maintain the comment-id. // if we do comment-id only based on MD5: // @@ -97,6 +103,21 @@ const ( YEAR_NANO_TS = NanoTS(365*86400) * TS_TO_NANO_TS ) +type BoardArticleID string + +func ToBoardArticleID(boardID bbs.BBoardID, articleID bbs.ArticleID) BoardArticleID { + return BoardArticleID(string(boardID) + ":" + string(articleID)) +} + +func (b BoardArticleID) Deserialize() (boardID bbs.BBoardID, articleID bbs.ArticleID, err error) { + theList := strings.Split(string(b), ":") + if len(theList) != 2 { + return "", "", bbs.ErrInvalidBBoardID + } + + return bbs.BBoardID(theList[0]), bbs.ArticleID(theList[1]), nil +} + type NanoTS int64 func Time4ToNanoTS(t pttbbstypes.Time4) NanoTS {