Repository: Ptt-official-app/Ptt-backend (go)

Issue:
[主線] [PTT] 實作上下箭頭推文 - usecase
### 實作細節 / Details of Implement
<!--  大致上描述要用什麼演算法下去做，盡可能在三項以內說完，可能的話粗估時間複雜度大概如何 -->
- 參照主 issue #210 說明 
- 修改usecase /article.go
   -  增加類似UpdateUsefulness function，以模仿stackoverflow或reddit
- 對應測試需要撰寫
### 期程 / Schedule
- 討論時間： 二天, 到 6/23
- 實作時間： 一天, 到 6/24
- 確認時間： 二天, 到 6/26

### 相關文件 / Documents
[API文件](https://docs.google.com/document/d/18DsZOyrlr5BIl2kKxZH7P2QxFLG02xL2SO0PzVHVY3k/edit#heading=h.r3t24bsqupvy)


Pull request:
Added UpdateUsefuleness in usecase
## 👏 解決掉的 issue / Resolved Issues
- close #231

## 📝 相關的 issue / Related Issues
- #210
- #232

## ⛏ 變更內容 / Details of Changes
<!-- 簡要陳列您的修改 -->
<!-- List down your changes concisely -->
- 增加usecase裏的UpdateUsefulness

Patch:
diff --git a/internal/repository/article.go b/internal/repository/article.go
index f129e80..44fbbba 100644
--- a/internal/repository/article.go
+++ b/internal/repository/article.go
@@ -25,6 +25,34 @@ type PushRecord interface {
 	Time() time.Time
 }
 
+type Push struct {
+	appendType string
+	id         string
+	ipAddr     string
+	text       string
+	time       time.Time
+}
+
+func (p *Push) Type() string {
+	return p.appendType
+}
+
+func (p *Push) ID() string {
+	return p.id
+}
+
+func (p *Push) IPAddr() string {
+	return p.ipAddr
+}
+
+func (p *Push) Text() string {
+	return p.text
+}
+
+func (p *Push) Time() time.Time {
+	return p.time
+}
+
 func (repo *repository) GetPopularArticles(ctx context.Context) ([]PopularArticleRecord, error) {
 	// Note: go-bbs has not implemented this yet
 	// TODO: delegate to repo.db when it is ready
@@ -37,7 +65,15 @@ func (repo *repository) AppendComment(ctx context.Context, userID, boardID, file
 	if err != nil {
 		return nil, err
 	}
-	return nil, nil
+
+	p := &Push{
+		appendType: appendType,
+		id:         userID,
+		ipAddr:     "", // not sure how to get IPAddr
+		text:       text,
+		time:       time.Now(),
+	}
+	return p, nil
 }
 
 func (repo *repository) AppendArticle(ctx context.Context, userID, boardID, title, content string) (bbs.ArticleRecord, error) {
diff --git a/internal/usecase/article.go b/internal/usecase/article.go
index 5cfc79e..0e4d6eb 100644
--- a/internal/usecase/article.go
+++ b/internal/usecase/article.go
@@ -3,6 +3,8 @@ package usecase
 import (
 	"context"
 	"fmt"
+	"strings"
+	"unicode/utf8"
 
 	"github.com/Ptt-official-app/go-bbs"
 
@@ -19,7 +21,60 @@ func (usecase *usecase) GetPopularArticles(ctx context.Context) ([]repository.Po
 }
 
 func (usecase *usecase) UpdateUsefulness(ctx context.Context, userID, boardID, filename, appendType string) (repository.PushRecord, error) {
-	return nil, nil
+	articleRecords, err := usecase.repo.GetBoardArticleRecords(ctx, boardID)
+
+	if err != nil {
+		return nil, fmt.Errorf("UpdateUsefulness error: %w", err)
+	}
+
+	var owner string
+	for _, record := range articleRecords {
+		if record.Filename() == filename {
+			owner = record.Owner()
+		}
+	}
+
+	if owner == userID {
+		return nil, fmt.Errorf("UpdateUsefuleness error: Owners cannot push their own article")
+	}
+
+	article, err := usecase.GetBoardArticle(ctx, boardID, filename)
+	if err != nil {
+		return nil, fmt.Errorf("UpdateUsefulness error: %w", err)
+	}
+
+	articleStr := string(article)
+
+	cur := 0
+	numRecommend := 0
+	for {
+		cur = strings.Index(articleStr[cur:], userID)
+		if cur < 0 {
+			break
+		}
+
+		r, _ := utf8.DecodeLastRuneInString(articleStr[:cur])
+		if r == utf8.RuneError {
+			return nil, fmt.Errorf("UpdateUsefulness error: DecodeLastRuneError")
+		}
+		if numRecommend < 1 && string(r) == "\u2191" {
+			numRecommend++
+		}
+
+		if numRecommend > -1 && string(r) == "\u2193" {
+			numRecommend--
+		}
+	}
+
+	if (appendType == "\u2191" && numRecommend == 1) || (appendType == "\u2193" && numRecommend == -1) {
+		return nil, fmt.Errorf("User with userID:%s has already pushed", userID)
+	}
+
+	p, err := usecase.repo.AppendComment(ctx, userID, boardID, filename, appendType, "")
+	if err != nil {
+		return nil, fmt.Errorf("UpdateUsefulness error: %w", err)
+	}
+	return p, nil
 }
 
 // AppendComment append comment to specific article


Test patch:
diff --git a/internal/usecase/article_test.go b/internal/usecase/article_test.go
index 43214ef..db7d72e 100644
--- a/internal/usecase/article_test.go
+++ b/internal/usecase/article_test.go
@@ -26,6 +26,30 @@ func TestGetPopularArticles(t *testing.T) {
 	}
 }
 
+func TestUpdateUsefulness(t *testing.T) {
+	repo := &MockRepository{}
+	userID := "mockUserID"
+	boardID := "board1"
+	filename := "filename1"
+	appendType := "↑"
+
+	usecase := NewUsecase(&config.Config{}, repo)
+
+	record, err := usecase.UpdateUsefulness(context.TODO(), userID, boardID, filename, appendType)
+
+	if err != nil {
+		t.Errorf("UpdateUsefulness expected err == nil, got %v", err)
+	}
+
+	if record.Type() != appendType {
+		t.Errorf("Push record with incorrect appendType, want %s, get %s", appendType, record.Type())
+	}
+
+	if record.ID() != userID {
+		t.Errorf("Push record with incorrect userID, want %s, get %s", userID, record.ID())
+	}
+}
+
 func TestForwardArticleToEmail(t *testing.T) {
 	repo := &MockRepository{}