chore: update list memos

This commit is contained in:
Steven
2023-12-06 22:44:49 +08:00
parent 055a327b5e
commit fa6693a7ae
8 changed files with 111 additions and 296 deletions

View File

@ -147,9 +147,7 @@ func (s *APIV1Service) registerMemoRoutes(g *echo.Group) {
// @Router /api/v1/memo [GET] // @Router /api/v1/memo [GET]
func (s *APIV1Service) GetMemoList(c echo.Context) error { func (s *APIV1Service) GetMemoList(c echo.Context) error {
ctx := c.Request().Context() ctx := c.Request().Context()
hasParentFlag := false
find := &store.FindMemo{ find := &store.FindMemo{
HasParent: &hasParentFlag,
OrderByPinned: true, OrderByPinned: true,
} }
if userID, err := util.ConvertStringToInt32(c.QueryParam("creatorId")); err == nil { if userID, err := util.ConvertStringToInt32(c.QueryParam("creatorId")); err == nil {
@ -450,10 +448,7 @@ func (s *APIV1Service) CreateMemo(c echo.Context) error {
// - creatorUsername is listed at ./web/src/helpers/api.ts:82, but it's not present here // - creatorUsername is listed at ./web/src/helpers/api.ts:82, but it's not present here
func (s *APIV1Service) GetAllMemos(c echo.Context) error { func (s *APIV1Service) GetAllMemos(c echo.Context) error {
ctx := c.Request().Context() ctx := c.Request().Context()
hasParentFlag := false findMemoMessage := &store.FindMemo{}
findMemoMessage := &store.FindMemo{
HasParent: &hasParentFlag,
}
_, ok := c.Get(userIDContextKey).(int32) _, ok := c.Get(userIDContextKey).(int32)
if !ok { if !ok {
findMemoMessage.VisibilityList = []store.Visibility{store.Public} findMemoMessage.VisibilityList = []store.Visibility{store.Public}
@ -510,10 +505,8 @@ func (s *APIV1Service) GetAllMemos(c echo.Context) error {
func (s *APIV1Service) GetMemoStats(c echo.Context) error { func (s *APIV1Service) GetMemoStats(c echo.Context) error {
ctx := c.Request().Context() ctx := c.Request().Context()
normalStatus := store.Normal normalStatus := store.Normal
hasParentFlag := false
findMemoMessage := &store.FindMemo{ findMemoMessage := &store.FindMemo{
RowStatus: &normalStatus, RowStatus: &normalStatus,
HasParent: &hasParentFlag,
ExcludeContent: true, ExcludeContent: true,
} }
if creatorID, err := util.ConvertStringToInt32(c.QueryParam("creatorId")); err == nil { if creatorID, err := util.ConvertStringToInt32(c.QueryParam("creatorId")); err == nil {
@ -744,8 +737,16 @@ func (s *APIV1Service) UpdateMemo(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memoID)) return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memoID))
} }
memoMessage, err := s.convertMemoFromStore(ctx, memo)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo").SetInternal(err)
}
if patchMemoRequest.ResourceIDList != nil { if patchMemoRequest.ResourceIDList != nil {
addedResourceIDList, removedResourceIDList := getIDListDiff(memo.ResourceIDList, patchMemoRequest.ResourceIDList) originResourceIDList := []int32{}
for _, resource := range memoMessage.ResourceList {
originResourceIDList = append(originResourceIDList, resource.ID)
}
addedResourceIDList, removedResourceIDList := getIDListDiff(originResourceIDList, patchMemoRequest.ResourceIDList)
for _, resourceID := range addedResourceIDList { for _, resourceID := range addedResourceIDList {
if _, err := s.Store.UpdateResource(ctx, &store.UpdateResource{ if _, err := s.Store.UpdateResource(ctx, &store.UpdateResource{
ID: resourceID, ID: resourceID,
@ -764,15 +765,15 @@ func (s *APIV1Service) UpdateMemo(c echo.Context) error {
} }
if patchMemoRequest.RelationList != nil { if patchMemoRequest.RelationList != nil {
patchMemoRelationList := make([]*store.MemoRelation, 0) patchMemoRelationList := make([]*MemoRelation, 0)
for _, memoRelation := range patchMemoRequest.RelationList { for _, memoRelation := range patchMemoRequest.RelationList {
patchMemoRelationList = append(patchMemoRelationList, &store.MemoRelation{ patchMemoRelationList = append(patchMemoRelationList, &MemoRelation{
MemoID: memo.ID, MemoID: memo.ID,
RelatedMemoID: memoRelation.RelatedMemoID, RelatedMemoID: memoRelation.RelatedMemoID,
Type: store.MemoRelationType(memoRelation.Type), Type: memoRelation.Type,
}) })
} }
addedMemoRelationList, removedMemoRelationList := getMemoRelationListDiff(memo.RelationList, patchMemoRelationList) addedMemoRelationList, removedMemoRelationList := getMemoRelationListDiff(memoMessage.RelationList, patchMemoRelationList)
for _, memoRelation := range addedMemoRelationList { for _, memoRelation := range addedMemoRelationList {
if _, err := s.Store.UpsertMemoRelation(ctx, memoRelation); err != nil { if _, err := s.Store.UpsertMemoRelation(ctx, memoRelation); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
@ -810,7 +811,7 @@ func (s *APIV1Service) UpdateMemo(c echo.Context) error {
} }
func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Memo) (*Memo, error) { func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Memo) (*Memo, error) {
memoResponse := &Memo{ memoMessage := &Memo{
ID: memo.ID, ID: memo.ID,
RowStatus: RowStatus(memo.RowStatus.String()), RowStatus: RowStatus(memo.RowStatus.String()),
CreatorID: memo.CreatorID, CreatorID: memo.CreatorID,
@ -823,63 +824,81 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
// Compose creator name. // Compose creator name.
user, err := s.Store.GetUser(ctx, &store.FindUser{ user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &memoResponse.CreatorID, ID: &memoMessage.CreatorID,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
if user.Nickname != "" { if user.Nickname != "" {
memoResponse.CreatorName = user.Nickname memoMessage.CreatorName = user.Nickname
} else { } else {
memoResponse.CreatorName = user.Username memoMessage.CreatorName = user.Username
} }
memoResponse.CreatorUsername = user.Username memoMessage.CreatorUsername = user.Username
// Compose display ts. // Compose display ts.
memoResponse.DisplayTs = memoResponse.CreatedTs memoMessage.DisplayTs = memoMessage.CreatedTs
// Find memo display with updated ts setting. // Find memo display with updated ts setting.
memoDisplayWithUpdatedTs, err := s.getMemoDisplayWithUpdatedTsSettingValue(ctx) memoDisplayWithUpdatedTs, err := s.getMemoDisplayWithUpdatedTsSettingValue(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if memoDisplayWithUpdatedTs { if memoDisplayWithUpdatedTs {
memoResponse.DisplayTs = memoResponse.UpdatedTs memoMessage.DisplayTs = memoMessage.UpdatedTs
} }
// Compose related resources.
resourceList, err := s.Store.ListResources(ctx, &store.FindResource{
MemoID: &memo.ID,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to list resources")
}
memoMessage.ResourceList = []*Resource{}
for _, resource := range resourceList {
memoMessage.ResourceList = append(memoMessage.ResourceList, convertResourceFromStore(resource))
}
// Compose related memo relations.
relationList := []*MemoRelation{} relationList := []*MemoRelation{}
for _, relation := range memo.RelationList { tempList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo.ID,
})
if err != nil {
return nil, err
}
for _, relation := range tempList {
relationList = append(relationList, convertMemoRelationFromStore(relation)) relationList = append(relationList, convertMemoRelationFromStore(relation))
} }
memoResponse.RelationList = relationList tempList, err = s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
RelatedMemoID: &memo.ID,
resourceList := []*Resource{} })
for _, resourceID := range memo.ResourceIDList { if err != nil {
resource, err := s.Store.GetResource(ctx, &store.FindResource{ return nil, err
ID: &resourceID,
})
if resource != nil && err == nil {
resourceList = append(resourceList, convertResourceFromStore(resource))
}
} }
memoResponse.ResourceList = resourceList for _, relation := range tempList {
relationList = append(relationList, convertMemoRelationFromStore(relation))
if memo.ParentID != nil { }
parentMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{ memoMessage.RelationList = relationList
ID: memo.ParentID, for _, relation := range memoMessage.RelationList {
}) if relation.MemoID == memoMessage.ID && relation.Type == MemoRelationComment {
if err != nil { parentMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{
return nil, err ID: &relation.RelatedMemoID,
} })
if parentMemo != nil {
parent, err := s.convertMemoFromStore(ctx, parentMemo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
memoResponse.Parent = parent if parentMemo != nil {
parent, err := s.convertMemoFromStore(ctx, parentMemo)
if err != nil {
return nil, err
}
memoMessage.Parent = parent
}
} }
} }
return memoResponse, nil return memoMessage, nil
} }
func (s *APIV1Service) getMemoDisplayWithUpdatedTsSettingValue(ctx context.Context) (bool, error) { func (s *APIV1Service) getMemoDisplayWithUpdatedTsSettingValue(ctx context.Context) (bool, error) {
@ -912,7 +931,7 @@ func convertCreateMemoRequestToMemoMessage(memoCreate *CreateMemoRequest) *store
} }
} }
func getMemoRelationListDiff(oldList, newList []*store.MemoRelation) (addedList, removedList []*store.MemoRelation) { func getMemoRelationListDiff(oldList, newList []*MemoRelation) (addedList, removedList []*store.MemoRelation) {
oldMap := map[string]bool{} oldMap := map[string]bool{}
for _, relation := range oldList { for _, relation := range oldList {
oldMap[fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)] = true oldMap[fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)] = true
@ -924,13 +943,21 @@ func getMemoRelationListDiff(oldList, newList []*store.MemoRelation) (addedList,
for _, relation := range oldList { for _, relation := range oldList {
key := fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type) key := fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)
if !newMap[key] { if !newMap[key] {
removedList = append(removedList, relation) removedList = append(removedList, &store.MemoRelation{
MemoID: relation.MemoID,
RelatedMemoID: relation.RelatedMemoID,
Type: store.MemoRelationType(relation.Type),
})
} }
} }
for _, relation := range newList { for _, relation := range newList {
key := fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type) key := fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)
if !oldMap[key] { if !oldMap[key] {
addedList = append(addedList, relation) addedList = append(addedList, &store.MemoRelation{
MemoID: relation.MemoID,
RelatedMemoID: relation.RelatedMemoID,
Type: store.MemoRelationType(relation.Type),
})
} }
} }
return addedList, removedList return addedList, removedList

View File

@ -12,7 +12,6 @@ import (
"github.com/gorilla/feeds" "github.com/gorilla/feeds"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/pkg/errors"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"github.com/usememos/memos/internal/util" "github.com/usememos/memos/internal/util"
@ -114,25 +113,19 @@ func (s *APIV1Service) generateRSSFromMemoList(ctx context.Context, memoList []*
var itemCountLimit = util.Min(len(memoList), maxRSSItemCount) var itemCountLimit = util.Min(len(memoList), maxRSSItemCount)
feed.Items = make([]*feeds.Item, itemCountLimit) feed.Items = make([]*feeds.Item, itemCountLimit)
for i := 0; i < itemCountLimit; i++ { for i := 0; i < itemCountLimit; i++ {
memo := memoList[i] memoMessage, err := s.convertMemoFromStore(ctx, memoList[i])
feed.Items[i] = &feeds.Item{ if err != nil {
Title: getRSSItemTitle(memo.Content), return "", err
Link: &feeds.Link{Href: baseURL + "/m/" + fmt.Sprintf("%d", memo.ID)},
Description: getRSSItemDescription(memo.Content),
Created: time.Unix(memo.CreatedTs, 0),
Enclosure: &feeds.Enclosure{Url: baseURL + "/m/" + fmt.Sprintf("%d", memo.ID) + "/image"},
} }
if len(memo.ResourceIDList) > 0 { feed.Items[i] = &feeds.Item{
resourceID := memo.ResourceIDList[0] Title: getRSSItemTitle(memoMessage.Content),
resource, err := s.Store.GetResource(ctx, &store.FindResource{ Link: &feeds.Link{Href: baseURL + "/m/" + fmt.Sprintf("%d", memoMessage.ID)},
ID: &resourceID, Description: getRSSItemDescription(memoMessage.Content),
}) Created: time.Unix(memoMessage.CreatedTs, 0),
if err != nil { Enclosure: &feeds.Enclosure{Url: baseURL + "/m/" + fmt.Sprintf("%d", memoMessage.ID) + "/image"},
return "", err }
} if len(memoMessage.ResourceList) > 0 {
if resource == nil { resource := memoMessage.ResourceList[0]
return "", errors.Errorf("Resource not found: %d", resourceID)
}
enclosure := feeds.Enclosure{} enclosure := feeds.Enclosure{}
if resource.ExternalLink != "" { if resource.ExternalLink != "" {
enclosure.Url = resource.ExternalLink enclosure.Url = resource.ExternalLink

View File

@ -8,7 +8,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
@ -107,7 +106,17 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
} }
orders = append(orders, "`id` DESC") orders = append(orders, "`id` DESC")
query := "SELECT `memo`.`id` AS `id`, `memo`.`creator_id` AS `creator_id`, UNIX_TIMESTAMP(`memo`.`created_ts`) AS `created_ts`, UNIX_TIMESTAMP(`memo`.`updated_ts`) AS `updated_ts`, `memo`.`row_status` AS `row_status`, `memo`.`content` AS `content`, `memo`.`visibility` AS `visibility`, MAX(CASE WHEN `memo_organizer`.`pinned` = 1 THEN 1 ELSE 0 END) AS `pinned`, GROUP_CONCAT(`resource`.`id`) AS `resource_id_list`, (SELECT GROUP_CONCAT(`memo_id`,':',`related_memo_id`,':',`type`) FROM `memo_relation` WHERE `memo_relation`.`memo_id` = `memo`.`id` OR `memo_relation`.`related_memo_id` = `memo`.`id` ) AS `relation_list` FROM `memo` LEFT JOIN `memo_organizer` ON `memo`.`id` = `memo_organizer`.`memo_id` LEFT JOIN `resource` ON `memo`.`id` = `resource`.`memo_id` WHERE " + strings.Join(where, " AND ") + " GROUP BY `memo`.`id` ORDER BY " + strings.Join(orders, ", ") fields := []string{
"`memo`.`id` AS `id`",
"`memo`.`creator_id` AS `creator_id`",
"UNIX_TIMESTAMP(`memo`.`created_ts`) AS `created_ts`",
"UNIX_TIMESTAMP(`memo`.`updated_ts`) AS `updated_ts`",
"`memo`.`row_status` AS `row_status`",
"`memo`.`content` AS `content`",
"`memo`.`visibility` AS `visibility`",
"MAX(CASE WHEN `memo_organizer`.`pinned` = 1 THEN 1 ELSE 0 END) AS `pinned`",
}
query := "SELECT " + strings.Join(fields, ",\n") + " FROM `memo` LEFT JOIN `memo_organizer` ON `memo`.`id` = `memo_organizer`.`memo_id` WHERE " + strings.Join(where, " AND ") + " GROUP BY `memo`.`id` ORDER BY " + strings.Join(orders, ", ")
if find.Limit != nil { if find.Limit != nil {
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit) query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
if find.Offset != nil { if find.Offset != nil {
@ -124,8 +133,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
list := make([]*store.Memo, 0) list := make([]*store.Memo, 0)
for rows.Next() { for rows.Next() {
var memo store.Memo var memo store.Memo
var memoResourceIDList sql.NullString
var memoRelationList sql.NullString
if err := rows.Scan( if err := rows.Scan(
&memo.ID, &memo.ID,
&memo.CreatorID, &memo.CreatorID,
@ -135,51 +142,9 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
&memo.Content, &memo.Content,
&memo.Visibility, &memo.Visibility,
&memo.Pinned, &memo.Pinned,
&memoResourceIDList,
&memoRelationList,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
if memoResourceIDList.Valid {
idStringList := strings.Split(memoResourceIDList.String, ",")
memo.ResourceIDList = make([]int32, 0, len(idStringList))
for _, idString := range idStringList {
id, err := util.ConvertStringToInt32(idString)
if err != nil {
return nil, err
}
memo.ResourceIDList = append(memo.ResourceIDList, id)
}
}
if memoRelationList.Valid {
memo.RelationList = make([]*store.MemoRelation, 0)
relatedMemoTypeList := strings.Split(memoRelationList.String, ",")
for _, relatedMemoType := range relatedMemoTypeList {
relatedMemoTypeList := strings.Split(relatedMemoType, ":")
if len(relatedMemoTypeList) != 3 {
return nil, errors.Errorf("invalid relation format")
}
memoID, err := util.ConvertStringToInt32(relatedMemoTypeList[0])
if err != nil {
return nil, err
}
relatedMemoID, err := util.ConvertStringToInt32(relatedMemoTypeList[1])
if err != nil {
return nil, err
}
relationType := store.MemoRelationType(relatedMemoTypeList[2])
memo.RelationList = append(memo.RelationList, &store.MemoRelation{
MemoID: memoID,
RelatedMemoID: relatedMemoID,
Type: relationType,
})
// Set the first parent ID if relation type is comment.
if memo.ParentID == nil && memoID == memo.ID && relationType == store.MemoRelationComment {
memo.ParentID = &relatedMemoID
}
}
}
list = append(list, &memo) list = append(list, &memo)
} }

View File

@ -10,7 +10,6 @@ import (
"github.com/Masterminds/squirrel" "github.com/Masterminds/squirrel"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
@ -84,9 +83,7 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
"memo.row_status AS row_status", "memo.row_status AS row_status",
"memo.content AS content", "memo.content AS content",
"memo.visibility AS visibility", "memo.visibility AS visibility",
"MAX(CASE WHEN memo_organizer.pinned = 1 THEN 1 ELSE 0 END) AS pinned", "MAX(CASE WHEN memo_organizer.pinned = 1 THEN 1 ELSE 0 END) AS pinned").
"string_agg(CAST(resource.id AS TEXT), ',') AS resource_id_list", // Cast to TEXT
"(SELECT string_agg(CAST(memo_id AS TEXT) || ':' || CAST(related_memo_id AS TEXT) || ':' || type, ',') FROM memo_relation WHERE memo_relation.memo_id = memo.id OR memo_relation.related_memo_id = memo.id) AS relation_list"). // Cast IDs to TEXT
From("memo"). From("memo").
LeftJoin("memo_organizer ON memo.id = memo_organizer.memo_id"). LeftJoin("memo_organizer ON memo.id = memo_organizer.memo_id").
LeftJoin("resource ON memo.id = resource.memo_id"). LeftJoin("resource ON memo.id = resource.memo_id").
@ -164,8 +161,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
updatedTsPlaceHolder, createdTsPlaceHolder := make([]uint8, 8), make([]uint8, 8) updatedTsPlaceHolder, createdTsPlaceHolder := make([]uint8, 8), make([]uint8, 8)
for rows.Next() { for rows.Next() {
var memo store.Memo var memo store.Memo
var memoResourceIDList sql.NullString
var memoRelationList sql.NullString
if err := rows.Scan( if err := rows.Scan(
&memo.ID, &memo.ID,
&memo.CreatorID, &memo.CreatorID,
@ -175,8 +170,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
&memo.Content, &memo.Content,
&memo.Visibility, &memo.Visibility,
&memo.Pinned, &memo.Pinned,
&memoResourceIDList,
&memoRelationList,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -185,45 +178,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
memo.CreatedTs = int64(binary.BigEndian.Uint64(createdTsPlaceHolder)) memo.CreatedTs = int64(binary.BigEndian.Uint64(createdTsPlaceHolder))
memo.UpdatedTs = int64(binary.BigEndian.Uint64(updatedTsPlaceHolder)) memo.UpdatedTs = int64(binary.BigEndian.Uint64(updatedTsPlaceHolder))
if memoResourceIDList.Valid {
idStringList := strings.Split(memoResourceIDList.String, ",")
memo.ResourceIDList = make([]int32, 0, len(idStringList))
for _, idString := range idStringList {
id, err := util.ConvertStringToInt32(idString)
if err != nil {
return nil, err
}
memo.ResourceIDList = append(memo.ResourceIDList, id)
}
}
if memoRelationList.Valid {
memo.RelationList = make([]*store.MemoRelation, 0)
relatedMemoTypeList := strings.Split(memoRelationList.String, ",")
for _, relatedMemoType := range relatedMemoTypeList {
relatedMemoTypeList := strings.Split(relatedMemoType, ":")
if len(relatedMemoTypeList) != 3 {
return nil, errors.Errorf("invalid relation format")
}
memoID, err := util.ConvertStringToInt32(relatedMemoTypeList[0])
if err != nil {
return nil, err
}
relatedMemoID, err := util.ConvertStringToInt32(relatedMemoTypeList[1])
if err != nil {
return nil, err
}
relationType := store.MemoRelationType(relatedMemoTypeList[2])
memo.RelationList = append(memo.RelationList, &store.MemoRelation{
MemoID: memoID,
RelatedMemoID: relatedMemoID,
Type: relationType,
})
// Set the first parent ID if relation type is comment.
if memo.ParentID == nil && memoID == memo.ID && relationType == store.MemoRelationComment {
memo.ParentID = &relatedMemoID
}
}
}
list = append(list, &memo) list = append(list, &memo)
} }

View File

@ -6,9 +6,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/pkg/errors"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
@ -88,13 +85,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
if v := find.Pinned; v != nil { if v := find.Pinned; v != nil {
where = append(where, "memo_organizer.pinned = 1") where = append(where, "memo_organizer.pinned = 1")
} }
if v := find.HasParent; v != nil {
if *v {
where = append(where, "parent_id IS NOT NULL")
} else {
where = append(where, "parent_id IS NULL")
}
}
orders := []string{} orders := []string{}
if find.OrderByPinned { if find.OrderByPinned {
@ -108,45 +98,21 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
orders = append(orders, "id DESC") orders = append(orders, "id DESC")
fields := []string{ fields := []string{
`memo.id AS id,`, `memo.id AS id`,
`memo.creator_id AS creator_id,`, `memo.creator_id AS creator_id`,
`memo.created_ts AS created_ts,`, `memo.created_ts AS created_ts`,
`memo.updated_ts AS updated_ts,`, `memo.updated_ts AS updated_ts`,
`memo.row_status AS row_status,`, `memo.row_status AS row_status`,
`memo.visibility AS visibility,`, `memo.visibility AS visibility`,
`CASE WHEN memo_organizer.pinned = 1 THEN 1 ELSE 0 END AS pinned`,
} }
if !find.ExcludeContent { if !find.ExcludeContent {
fields = append(fields, `memo.content AS content,`) fields = append(fields, `memo.content AS content`)
} }
query := ` query := `SELECT ` + strings.Join(fields, ", ") + `
SELECT FROM memo
` + strings.Join(fields, "\n") + ` LEFT JOIN memo_organizer ON memo.id = memo_organizer.memo_id
CASE WHEN mo.pinned = 1 THEN 1 ELSE 0 END AS pinned,
(
SELECT
related_memo_id
FROM
memo_relation
WHERE
memo_relation.memo_id = memo.id AND memo_relation.type = 'COMMENT'
LIMIT 1
) AS parent_id,
GROUP_CONCAT(resource.id) AS resource_id_list,
(
SELECT
GROUP_CONCAT(memo_relation.memo_id || ':' || memo_relation.related_memo_id || ':' || memo_relation.type)
FROM
memo_relation
WHERE
memo_relation.memo_id = memo.id OR memo_relation.related_memo_id = memo.id
) AS relation_list
FROM
memo
LEFT JOIN
memo_organizer mo ON memo.id = mo.memo_id
LEFT JOIN
resource ON memo.id = resource.memo_id
WHERE ` + strings.Join(where, " AND ") + ` WHERE ` + strings.Join(where, " AND ") + `
GROUP BY memo.id GROUP BY memo.id
ORDER BY ` + strings.Join(orders, ", ") ORDER BY ` + strings.Join(orders, ", ")
@ -166,8 +132,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
list := make([]*store.Memo, 0) list := make([]*store.Memo, 0)
for rows.Next() { for rows.Next() {
var memo store.Memo var memo store.Memo
var memoResourceIDList sql.NullString
var memoRelationList sql.NullString
dests := []any{ dests := []any{
&memo.ID, &memo.ID,
&memo.CreatorID, &memo.CreatorID,
@ -175,50 +139,14 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
&memo.UpdatedTs, &memo.UpdatedTs,
&memo.RowStatus, &memo.RowStatus,
&memo.Visibility, &memo.Visibility,
&memo.Pinned,
} }
if !find.ExcludeContent { if !find.ExcludeContent {
dests = append(dests, &memo.Content) dests = append(dests, &memo.Content)
} }
dests = append(dests, &memo.Pinned, &memo.ParentID, &memoResourceIDList, &memoRelationList)
if err := rows.Scan(dests...); err != nil { if err := rows.Scan(dests...); err != nil {
return nil, err return nil, err
} }
if memoResourceIDList.Valid {
idStringList := strings.Split(memoResourceIDList.String, ",")
memo.ResourceIDList = make([]int32, 0, len(idStringList))
for _, idString := range idStringList {
id, err := util.ConvertStringToInt32(idString)
if err != nil {
return nil, err
}
memo.ResourceIDList = append(memo.ResourceIDList, id)
}
}
if memoRelationList.Valid {
memo.RelationList = make([]*store.MemoRelation, 0)
relatedMemoTypeList := strings.Split(memoRelationList.String, ",")
for _, relatedMemoType := range relatedMemoTypeList {
relatedMemoTypeList := strings.Split(relatedMemoType, ":")
if len(relatedMemoTypeList) != 3 {
return nil, errors.New("invalid relation format")
}
memoID, err := util.ConvertStringToInt32(relatedMemoTypeList[0])
if err != nil {
return nil, err
}
relatedMemoID, err := util.ConvertStringToInt32(relatedMemoTypeList[1])
if err != nil {
return nil, err
}
relationType := store.MemoRelationType(relatedMemoTypeList[2])
memo.RelationList = append(memo.RelationList, &store.MemoRelation{
MemoID: memoID,
RelatedMemoID: relatedMemoID,
Type: relationType,
})
}
}
list = append(list, &memo) list = append(list, &memo)
} }

View File

@ -42,12 +42,7 @@ type Memo struct {
Visibility Visibility Visibility Visibility
// Composed fields // Composed fields
// For those comment memos, the parent ID is the memo ID of the memo being commented. Pinned bool
// If the parent ID is nil, then this memo is not a comment.
ParentID *int32
Pinned bool
ResourceIDList []int32
RelationList []*MemoRelation
} }
type FindMemo struct { type FindMemo struct {
@ -63,7 +58,6 @@ type FindMemo struct {
ContentSearch []string ContentSearch []string
VisibilityList []Visibility VisibilityList []Visibility
Pinned *bool Pinned *bool
HasParent *bool
ExcludeContent bool ExcludeContent bool
// Pagination // Pagination

View File

@ -39,19 +39,6 @@ func (s *Store) ListMemoRelations(ctx context.Context, find *FindMemoRelation) (
return s.driver.ListMemoRelations(ctx, find) return s.driver.ListMemoRelations(ctx, find)
} }
func (s *Store) GetMemoRelation(ctx context.Context, find *FindMemoRelation) (*MemoRelation, error) {
list, err := s.ListMemoRelations(ctx, find)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, nil
}
return list[0], nil
}
func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelation) error { func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelation) error {
return s.driver.DeleteMemoRelation(ctx, delete) return s.driver.DeleteMemoRelation(ctx, delete)
} }

View File

@ -55,37 +55,4 @@ func TestMemoRelationStore(t *testing.T) {
} }
_, err = ts.UpsertMemoRelation(ctx, commentRelation) _, err = ts.UpsertMemoRelation(ctx, commentRelation)
require.NoError(t, err) require.NoError(t, err)
memo, err = ts.GetMemo(ctx, &store.FindMemo{
ID: &memo.ID,
})
require.NoError(t, err)
require.Equal(t, 2, len(memo.RelationList))
require.Equal(t, referenceRelation, memo.RelationList[0])
require.Equal(t, commentRelation, memo.RelationList[1])
relatedMemo, err = ts.GetMemo(ctx, &store.FindMemo{
ID: &relatedMemo.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(relatedMemo.RelationList))
require.Equal(t, referenceRelation, relatedMemo.RelationList[0])
commentMemo, err = ts.GetMemo(ctx, &store.FindMemo{
ID: &commentMemo.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(commentMemo.RelationList))
require.Equal(t, commentRelation, commentMemo.RelationList[0])
err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: relatedMemo.ID,
})
require.NoError(t, err)
err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: commentMemo.ID,
})
require.NoError(t, err)
memoRelation, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo.ID,
})
require.NoError(t, err)
require.Equal(t, 0, len(memoRelation))
} }