Skip to content

Commit

Permalink
Merge pull request #269 from tingyuchang/delete_article
Browse files Browse the repository at this point in the history
[WIP] Delete article
  • Loading branch information
chhsiao1981 authored Oct 29, 2021
2 parents 18e1cf9 + f57081f commit de64760
Show file tree
Hide file tree
Showing 11 changed files with 493 additions and 0 deletions.
45 changes: 45 additions & 0 deletions api/delete_articles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package api

import (
"github.com/Ptt-official-app/go-pttbbs/bbs"
"github.com/Ptt-official-app/go-pttbbs/ptttype"
"github.com/gin-gonic/gin"
)

const DELETE_ARTICLES_R = "/board/:bid/deletearticles"

type DeleteArticlesParams struct {
ArticleIDs []bbs.ArticleID `json:"aids" form:"aids" url:"aids" binding:"required"`
}

type DeleteArticlesPath struct {
BBoardID bbs.BBoardID `uri:"bid" binding:"required"`
}

type DeleteArticlesResult struct {
Indexes []ptttype.SortIdx `json:"indexes"`
}

func DeleteArticlesWrapper(c *gin.Context) {
params := &DeleteArticlesParams{}
path := &DeleteArticlesPath{}
LoginRequiredPathJSON(DeleteArticles, params, path, c)
}

func DeleteArticles(remoteAddr string, uuserID bbs.UUserID, params interface{}, path interface{}) (result interface{}, err error) {
theParams, ok := params.(*DeleteArticlesParams)
if !ok {
return nil, ErrInvalidParams
}

thePath, ok := path.(*DeleteArticlesPath)
if !ok {
return nil, ErrInvalidPath
}

result, err = bbs.DeleteArticles(uuserID, thePath.BBoardID, theParams.ArticleIDs, remoteAddr)
if err != nil {
return nil, err
}
return DeleteArticlesResult{Indexes: result.([]ptttype.SortIdx)}, nil
}
118 changes: 118 additions & 0 deletions api/delete_articles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package api

import (
"encoding/binary"
"os"
"reflect"
"sync"
"testing"

"github.com/Ptt-official-app/go-pttbbs/bbs"
"github.com/Ptt-official-app/go-pttbbs/types"

"github.com/Ptt-official-app/go-pttbbs/ptttype"
)

func TestDeleteArticles(t *testing.T) {
setupTest(t.Name())
defer teardownTest(t.Name())
boardID0 := &ptttype.BoardID_t{}
copy(boardID0[:], []byte("10_WhoAmI"))

filename0 := &ptttype.Filename_t{}
copy(filename0[:], []byte("M.1607202239.A.30D"))

filename1 := &ptttype.Filename_t{}
copy(filename1[:], []byte("M.1607203395.A.00D"))

fileHeaderRaw1 := &ptttype.FileHeaderRaw{
Filename: ptttype.Filename_t{ // M.1607202239.A.30D
0x4d, 0x2e, 0x31, 0x36, 0x30, 0x37, 0x32, 0x30,
0x32, 0x32, 0x33, 0x39, 0x2e, 0x41, 0x2e, 0x33,
0x30, 0x44,
},
Modified: 1607202238,
Owner: ptttype.Owner_t{0x53, 0x59, 0x53, 0x4f, 0x50}, // SYSOP
Date: ptttype.Date_t{0x31, 0x32, 0x2f, 0x30, 0x36}, // 12/06
Title: ptttype.Title_t{ //[問題] 我是誰?~
0x5b, 0xb0, 0xdd, 0xc3, 0x44, 0x5d, 0x20, 0xa7,
0xda, 0xac, 0x4f, 0xbd, 0xd6, 0xa1, 0x48, 0xa1,
0xe3,
},
}
fileHeaderRaw2 := &ptttype.FileHeaderRaw{
Filename: ptttype.Filename_t{ // M.1607203395.A.00D
0x4d, 0x2e, 0x31, 0x36, 0x30, 0x37, 0x32, 0x30,
0x33, 0x33, 0x39, 0x35, 0x2e, 0x41, 0x2e, 0x30,
0x30, 0x44,
},
Modified: 1607203394,
Owner: ptttype.Owner_t{0x53, 0x59, 0x53, 0x4f, 0x50}, // SYSOP
Date: ptttype.Date_t{0x31, 0x32, 0x2f, 0x30, 0x36}, // 12/06
Title: ptttype.Title_t{ //[心得] 然後呢?~
0x5b, 0xa4, 0xdf, 0xb1, 0x6f, 0x5d, 0x20, 0xb5,
0x4d, 0xab, 0xe1, 0xa9, 0x4f, 0xa1, 0x48, 0xa1,
0xe3,
},
Filemode: ptttype.FILE_MULTI,
}
case_1_FileHeaders := []ptttype.FileHeaderRaw{
*fileHeaderRaw1, // M.1607202239.A.30D
*fileHeaderRaw2, // M.1607203395.A.00D
}
case_1_Filename := "./testcase/boards/W/WhoAmI/.DIR"
defer os.RemoveAll(case_1_Filename)
file, _ := os.OpenFile(case_1_Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
defer file.Close()
_ = types.BinaryWrite(file, binary.LittleEndian, case_1_FileHeaders)

params0 := &DeleteArticlesParams{
ArticleIDs: []bbs.ArticleID{bbs.ToArticleID(filename0)},
}

path0 := &DeleteArticlesPath{
BBoardID: "10_WhoAmI",
}

type args struct {
remoteAddr string
uuserID bbs.UUserID
params interface{}
path interface{}
}
tests := []struct {
name string
args args
wantResult interface{}
wantErr bool
}{
// TODO: Add test cases.
{
"",
args{
"127.0.0.1",
"SYSOP",
params0,
path0,
},
DeleteArticlesResult{Indexes: []ptttype.SortIdx{1}},
false,
},
}
var wg sync.WaitGroup
for _, tt := range tests {
wg.Add(1)
t.Run(tt.name, func(t *testing.T) {
wg.Done()
gotResult, err := DeleteArticles(tt.args.remoteAddr, tt.args.uuserID, tt.args.params, tt.args.path)
if (err != nil) != tt.wantErr {
t.Errorf("DeleteArticles() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotResult, tt.wantResult) {
t.Errorf("DeleteArticles() gotResult = %v, want %v", gotResult, tt.wantResult)
}
})
wg.Wait()
}
}
50 changes: 50 additions & 0 deletions bbs/delete_articles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package bbs

import (
"github.com/Ptt-official-app/go-pttbbs/ptt"
"github.com/Ptt-official-app/go-pttbbs/ptttype"
)

func DeleteArticles(uuserID UUserID, bboardID BBoardID, articleIDs []ArticleID, ip string) ([]ptttype.SortIdx, error) {
userIDRaw, err := uuserID.ToRaw()
if err != nil {
return nil, err
}

uid, userecRaw, err := ptt.InitCurrentUser(userIDRaw)
if err != nil {
return nil, err
}

bid, boardIDRaw, err := bboardID.ToRaw()
if err != nil {
return nil, err
}

var result []ptttype.SortIdx
for _, articleID := range articleIDs {
filename := articleID.ToFilename()
createTime, err := filename.CreateTime()
if err != nil {
return nil, err
}
startIdx, err := ptt.FindArticleStartIdx(userecRaw, uid, boardIDRaw, bid, createTime, filename, false)
if err != nil {
return nil, err
}
// FindArticleStartIdx only find nearest idx, so we must make sure filename is exactly correct
summariesRaw, _, _, _, _ := ptt.LoadGeneralArticles(userecRaw, uid, boardIDRaw, bid, startIdx, 1, true)
if len(summariesRaw) == 1 {
articleSummary := NewArticleSummaryFromRaw(bboardID, summariesRaw[0])
if articleID == articleSummary.ArticleID {
err = ptt.DeleteArticles(boardIDRaw, filename, startIdx)
// TODO is need recover deleted items if get error?
if err != nil {
return nil, err
}
result = append(result, startIdx)
}
}
}
return result, nil
}
120 changes: 120 additions & 0 deletions bbs/delete_articles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package bbs

import (
"encoding/binary"
"os"
"sync"
"testing"

"github.com/stretchr/testify/assert"

"github.com/Ptt-official-app/go-pttbbs/ptttype"
"github.com/Ptt-official-app/go-pttbbs/types"
)

func TestDeleteArticles(t *testing.T) {
setupTest() // SetBBSHOME("./testcase")
defer teardownTest()

boardID0 := &ptttype.BoardID_t{}
copy(boardID0[:], []byte("10_WhoAmI"))

filename0 := &ptttype.Filename_t{}
copy(filename0[:], []byte("M.1607202239.A.30D"))

filename1 := &ptttype.Filename_t{}
copy(filename1[:], []byte("M.1607203395.A.00D"))

fileHeaderRaw1 := &ptttype.FileHeaderRaw{
Filename: ptttype.Filename_t{ // M.1607202239.A.30D
0x4d, 0x2e, 0x31, 0x36, 0x30, 0x37, 0x32, 0x30,
0x32, 0x32, 0x33, 0x39, 0x2e, 0x41, 0x2e, 0x33,
0x30, 0x44,
},
Modified: 1607202238,
Owner: ptttype.Owner_t{0x53, 0x59, 0x53, 0x4f, 0x50}, // SYSOP
Date: ptttype.Date_t{0x31, 0x32, 0x2f, 0x30, 0x36}, // 12/06
Title: ptttype.Title_t{ //[問題] 我是誰?~
0x5b, 0xb0, 0xdd, 0xc3, 0x44, 0x5d, 0x20, 0xa7,
0xda, 0xac, 0x4f, 0xbd, 0xd6, 0xa1, 0x48, 0xa1,
0xe3,
},
}
fileHeaderRaw2 := &ptttype.FileHeaderRaw{
Filename: ptttype.Filename_t{ // M.1607203395.A.00D
0x4d, 0x2e, 0x31, 0x36, 0x30, 0x37, 0x32, 0x30,
0x33, 0x33, 0x39, 0x35, 0x2e, 0x41, 0x2e, 0x30,
0x30, 0x44,
},
Modified: 1607203394,
Owner: ptttype.Owner_t{0x53, 0x59, 0x53, 0x4f, 0x50}, // SYSOP
Date: ptttype.Date_t{0x31, 0x32, 0x2f, 0x30, 0x36}, // 12/06
Title: ptttype.Title_t{ //[心得] 然後呢?~
0x5b, 0xa4, 0xdf, 0xb1, 0x6f, 0x5d, 0x20, 0xb5,
0x4d, 0xab, 0xe1, 0xa9, 0x4f, 0xa1, 0x48, 0xa1,
0xe3,
},
Filemode: ptttype.FILE_MULTI,
}
case_1_FileHeaders := []ptttype.FileHeaderRaw{
*fileHeaderRaw1, // M.1607202239.A.30D
*fileHeaderRaw2, // M.1607203395.A.00D
}
case_1_Filename := "./testcase/boards/W/WhoAmI/.DIR"
defer os.RemoveAll(case_1_Filename)
file, _ := os.OpenFile(case_1_Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
defer file.Close()
_ = types.BinaryWrite(file, binary.LittleEndian, case_1_FileHeaders)

type args struct {
uuserID UUserID
bboardID BBoardID
articleIDs []ArticleID
ip string
}
tests := []struct {
name string
args args
results []ptttype.SortIdx
wantErr bool
}{
{
name: "test DeleteArticles w/o error",
args: args{
uuserID: "SYSOP",
bboardID: "10_WhoAmI",
articleIDs: []ArticleID{ToArticleID(filename0)},
ip: "127.0.0.1",
},
results: []ptttype.SortIdx{1},
wantErr: false,
},
{
name: "test DeleteArticles Delete index 2 article",
args: args{
uuserID: "SYSOP",
bboardID: "10_WhoAmI",
articleIDs: []ArticleID{ToArticleID(filename1)},
ip: "127.0.0.1",
},
results: []ptttype.SortIdx{2},
wantErr: false,
},
}
var wg sync.WaitGroup
for _, tt := range tests {
wg.Add(1)
t.Run(tt.name, func(t *testing.T) {
wg.Done()
idx, err := DeleteArticles(tt.args.uuserID, tt.args.bboardID, tt.args.articleIDs, tt.args.ip)
if (err != nil) != tt.wantErr {
t.Errorf("DeleteArticles() error = %v, wantErr %v", err, tt.wantErr)
}

if !assert.ElementsMatch(t, idx, tt.results) {
t.Errorf("DeleteArticles() result = %v, wanted %v", idx, tt.results)
}
})
wg.Wait()
}
}
26 changes: 26 additions & 0 deletions cmsys/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,3 +483,29 @@ func AppendRecord(filename string, data interface{}, theSize uintptr) (idx pttty

return idxInStore.ToSortIdx(), nil
}

func DeleteRecord(filename string, index ptttype.SortIdxInStore, theSize uintptr) error {
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, os.FileMode(ptttype.DEFAULT_FILE_CREATE_PERM))
if err != nil {
return err
}
defer file.Close()

fd := file.Fd()
err = GoFlock(fd, filename)
if err != nil {
return err
}
defer func() { _ = GoFunlock(fd, filename) }()

offset := int64(index) * int64(theSize)
_, err = file.Seek(offset, io.SeekStart)
if err != nil {
return err
}
err = types.BinaryWrite(file, binary.LittleEndian, []byte(ptttype.FN_SAFEDEL))
if err != nil {
return err
}
return nil
}
Loading

0 comments on commit de64760

Please sign in to comment.