Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
ea728d23
Unverified
Commit
ea728d23
authored
May 25, 2023
by
boojack
Committed by
GitHub
May 25, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: memo store (#1741)
parent
43819b02
Changes
16
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
518 additions
and
533 deletions
+518
-533
memo.go
api/memo.go
+10
-16
memo.go
server/memo.go
+215
-119
memo_relation.go
server/memo_relation.go
+8
-0
memo_resource.go
server/memo_resource.go
+2
-1
rss.go
server/rss.go
+18
-17
tag.go
server/tag.go
+5
-4
common.go
store/common.go
+21
-0
memo.go
store/memo.go
+196
-246
memo_relation.go
store/memo_relation.go
+0
-21
resource.go
store/resource.go
+0
-35
store.go
store/store.go
+0
-1
user.go
store/user.go
+4
-34
memo_relation_test.go
test/server/memo_relation_test.go
+2
-2
memo_test.go
test/server/memo_test.go
+16
-18
memo_relation_test.go
test/store/memo_relation_test.go
+7
-8
memo_test.go
test/store/memo_test.go
+14
-11
No files found.
api/memo.go
View file @
ea728d23
package
api
// MaxContentLength means the max memo content bytes is 1MB.
const
MaxContentLength
=
1
<<
30
// Visibility is the type of a visibility.
type
Visibility
string
...
...
@@ -15,8 +12,8 @@ const (
Private
Visibility
=
"PRIVATE"
)
func
(
e
Visibility
)
String
()
string
{
switch
e
{
func
(
v
Visibility
)
String
()
string
{
switch
v
{
case
Public
:
return
"PUBLIC"
case
Protected
:
...
...
@@ -27,7 +24,7 @@ func (e Visibility) String() string {
return
"PRIVATE"
}
type
Memo
struct
{
type
Memo
Response
struct
{
ID
int
`json:"id"`
// Standard fields
...
...
@@ -42,12 +39,13 @@ type Memo struct {
Pinned
bool
`json:"pinned"`
// Related fields
CreatorName
string
`json:"creatorName"`
ResourceList
[]
*
Resource
`json:"resourceList"`
RelationList
[]
*
MemoRelation
`json:"relationList"`
CreatorName
string
`json:"creatorName"`
ResourceIDList
[]
int
ResourceList
[]
*
Resource
`json:"resourceList"`
RelationList
[]
*
MemoRelation
`json:"relationList"`
}
type
MemoCreate
struct
{
type
CreateMemoRequest
struct
{
// Standard fields
CreatorID
int
`json:"-"`
CreatedTs
*
int64
`json:"createdTs"`
...
...
@@ -61,7 +59,7 @@ type MemoCreate struct {
RelationList
[]
*
MemoRelationUpsert
`json:"relationList"`
}
type
MemoPatch
struct
{
type
PatchMemoRequest
struct
{
ID
int
`json:"-"`
// Standard fields
...
...
@@ -78,7 +76,7 @@ type MemoPatch struct {
RelationList
[]
*
MemoRelationUpsert
`json:"relationList"`
}
type
MemoFind
struct
{
type
FindMemoRequest
struct
{
ID
*
int
// Standard fields
...
...
@@ -94,7 +92,3 @@ type MemoFind struct {
Limit
*
int
Offset
*
int
}
type
MemoDelete
struct
{
ID
int
}
server/memo.go
View file @
ea728d23
package
server
import
(
"context"
"encoding/json"
"fmt"
"net/http"
...
...
@@ -16,6 +17,9 @@ import (
"github.com/labstack/echo/v4"
)
// maxContentLength means the max memo content bytes is 1MB.
const
maxContentLength
=
1
<<
30
func
(
s
*
Server
)
registerMemoRoutes
(
g
*
echo
.
Group
)
{
g
.
POST
(
"/memo"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
...
...
@@ -24,12 +28,15 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Missing user in session"
)
}
memoCreate
:=
&
api
.
MemoCreate
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
memoCreate
);
err
!=
nil
{
createMemoRequest
:=
&
api
.
CreateMemoRequest
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
createMemoRequest
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted post memo request"
)
.
SetInternal
(
err
)
}
if
len
(
createMemoRequest
.
Content
)
>
maxContentLength
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Content size overflow, up to 1MB"
)
}
if
memoCreate
.
Visibility
==
""
{
if
createMemoRequest
.
Visibility
==
""
{
userMemoVisibilitySetting
,
err
:=
s
.
Store
.
FindUserSetting
(
ctx
,
&
api
.
UserSettingFind
{
UserID
:
userID
,
Key
:
api
.
UserSettingMemoVisibilityKey
,
...
...
@@ -44,14 +51,14 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal user setting value"
)
.
SetInternal
(
err
)
}
memoCreate
.
Visibility
=
memoVisibility
createMemoRequest
.
Visibility
=
memoVisibility
}
else
{
// Private is the default memo visibility.
memoCreate
.
Visibility
=
api
.
Private
createMemoRequest
.
Visibility
=
api
.
Private
}
}
// Find
system settings
// Find
disable public memos system setting.
disablePublicMemosSystemSetting
,
err
:=
s
.
Store
.
FindSystemSetting
(
ctx
,
&
api
.
SystemSettingFind
{
Name
:
api
.
SystemSettingDisablePublicMemosName
,
})
...
...
@@ -65,46 +72,40 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal system setting"
)
.
SetInternal
(
err
)
}
if
disablePublicMemos
{
// Allow if the user is an admin.
user
,
err
:=
s
.
Store
.
FindUser
(
ctx
,
&
api
.
UserFind
{
ID
:
&
userID
,
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find user"
)
.
SetInternal
(
err
)
}
// Only enforce private if you're a regular user.
// Admins should know what they're doing.
// Enforce normal user to create private memo if public memos are disabled.
if
user
.
Role
==
"USER"
{
memoCreate
.
Visibility
=
api
.
Private
createMemoRequest
.
Visibility
=
api
.
Private
}
}
}
if
len
(
memoCreate
.
Content
)
>
api
.
MaxContentLength
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Content size overflow, up to 1MB"
)
.
SetInternal
(
err
)
}
memoCreate
.
CreatorID
=
userID
memo
,
err
:=
s
.
Store
.
CreateMemo
(
ctx
,
memoCreate
)
createMemoRequest
.
CreatorID
=
userID
memoMessage
,
err
:=
s
.
Store
.
CreateMemo
(
ctx
,
convertCreateMemoRequestToMemoMessage
(
createMemoRequest
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to create memo"
)
.
SetInternal
(
err
)
}
if
err
:=
s
.
createMemoCreateActivity
(
c
,
memo
);
err
!=
nil
{
if
err
:=
s
.
createMemoCreateActivity
(
c
,
memo
Message
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to create activity"
)
.
SetInternal
(
err
)
}
for
_
,
resourceID
:=
range
memoCreate
.
ResourceIDList
{
for
_
,
resourceID
:=
range
createMemoRequest
.
ResourceIDList
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoResource
(
ctx
,
&
api
.
MemoResourceUpsert
{
MemoID
:
memo
.
ID
,
MemoID
:
memo
Message
.
ID
,
ResourceID
:
resourceID
,
});
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo resource"
)
.
SetInternal
(
err
)
}
}
for
_
,
memoRelationUpsert
:=
range
memoCreate
.
RelationList
{
for
_
,
memoRelationUpsert
:=
range
createMemoRequest
.
RelationList
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoRelation
(
ctx
,
&
store
.
MemoRelationMessage
{
MemoID
:
memo
.
ID
,
MemoID
:
memo
Message
.
ID
,
RelatedMemoID
:
memoRelationUpsert
.
RelatedMemoID
,
Type
:
store
.
MemoRelationType
(
memoRelationUpsert
.
Type
),
});
err
!=
nil
{
...
...
@@ -112,11 +113,17 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
}
}
memo
,
err
=
s
.
Store
.
ComposeMemo
(
ctx
,
memo
)
memoMessage
,
err
=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoMessage
.
ID
,
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memo
))
memoResponse
,
err
:=
s
.
ComposeMemoResponse
(
ctx
,
convertMemoMessageToMemoResponse
(
memoMessage
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memoResponse
))
})
g
.
PATCH
(
"/memo/:memoId"
,
func
(
c
echo
.
Context
)
error
{
...
...
@@ -131,47 +138,58 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"ID is not a number: %s"
,
c
.
Param
(
"memoId"
)))
.
SetInternal
(
err
)
}
memo
,
err
:=
s
.
Store
.
FindMemo
(
ctx
,
&
api
.
MemoFind
{
memo
Message
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoID
,
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find memo"
)
.
SetInternal
(
err
)
}
if
memo
.
CreatorID
!=
userID
{
if
memo
Message
.
CreatorID
!=
userID
{
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Unauthorized"
)
}
currentTs
:=
time
.
Now
()
.
Unix
()
memoPatch
:=
&
api
.
MemoPatch
{
patchMemoRequest
:=
&
api
.
PatchMemoRequest
{
ID
:
memoID
,
UpdatedTs
:
&
currentTs
,
}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
memoPatch
);
err
!=
nil
{
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
patchMemoRequest
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted patch memo request"
)
.
SetInternal
(
err
)
}
if
memoPatch
.
Content
!=
nil
&&
len
(
*
memoPatch
.
Content
)
>
api
.
M
axContentLength
{
if
patchMemoRequest
.
Content
!=
nil
&&
len
(
*
patchMemoRequest
.
Content
)
>
m
axContentLength
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Content size overflow, up to 1MB"
)
.
SetInternal
(
err
)
}
memo
,
err
=
s
.
Store
.
PatchMemo
(
ctx
,
memoPatch
)
updateMemoMessage
:=
&
store
.
UpdateMemoMessage
{
ID
:
memoID
,
CreatedTs
:
patchMemoRequest
.
CreatedTs
,
UpdatedTs
:
patchMemoRequest
.
UpdatedTs
,
Content
:
patchMemoRequest
.
Content
,
}
if
patchMemoRequest
.
RowStatus
!=
nil
{
rowStatus
:=
store
.
RowStatus
(
patchMemoRequest
.
RowStatus
.
String
())
updateMemoMessage
.
RowStatus
=
&
rowStatus
}
if
patchMemoRequest
.
Visibility
!=
nil
{
visibility
:=
store
.
Visibility
(
patchMemoRequest
.
Visibility
.
String
())
updateMemoMessage
.
Visibility
=
&
visibility
}
err
=
s
.
Store
.
UpdateMemo
(
ctx
,
updateMemoMessage
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to patch memo"
)
.
SetInternal
(
err
)
}
memo
,
err
=
s
.
Store
.
ComposeMemo
(
ctx
,
memo
)
memo
Message
,
err
=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoID
}
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to
compose
memo"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to
find
memo"
)
.
SetInternal
(
err
)
}
if
memoPatch
.
ResourceIDList
!=
nil
{
resourceIDList
:=
make
([]
int
,
0
)
for
_
,
resource
:=
range
memo
.
ResourceList
{
resourceIDList
=
append
(
resourceIDList
,
resource
.
ID
)
}
addedResourceIDList
,
removedResourceIDList
:=
getIDListDiff
(
resourceIDList
,
memoPatch
.
ResourceIDList
)
if
patchMemoRequest
.
ResourceIDList
!=
nil
{
addedResourceIDList
,
removedResourceIDList
:=
getIDListDiff
(
memoMessage
.
ResourceIDList
,
patchMemoRequest
.
ResourceIDList
)
for
_
,
resourceID
:=
range
addedResourceIDList
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoResource
(
ctx
,
&
api
.
MemoResourceUpsert
{
MemoID
:
memo
.
ID
,
MemoID
:
memo
Message
.
ID
,
ResourceID
:
resourceID
,
});
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo resource"
)
.
SetInternal
(
err
)
...
...
@@ -179,7 +197,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
}
for
_
,
resourceID
:=
range
removedResourceIDList
{
if
err
:=
s
.
Store
.
DeleteMemoResource
(
ctx
,
&
api
.
MemoResourceDelete
{
MemoID
:
&
memo
.
ID
,
MemoID
:
&
memo
Message
.
ID
,
ResourceID
:
&
resourceID
,
});
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to delete memo resource"
)
.
SetInternal
(
err
)
...
...
@@ -187,100 +205,106 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
}
}
if
memoPatch
.
RelationList
!=
nil
{
patchMemoRelationList
:=
make
([]
*
api
.
MemoRelation
,
0
)
for
_
,
memoRelation
Upsert
:=
range
memoPatch
.
RelationList
{
patchMemoRelationList
=
append
(
patchMemoRelationList
,
&
api
.
MemoRelation
{
MemoID
:
memo
.
ID
,
RelatedMemoID
:
memoRelation
Upsert
.
RelatedMemoID
,
Type
:
memoRelationUpsert
.
Type
,
if
patchMemoRequest
.
RelationList
!=
nil
{
patchMemoRelationList
:=
make
([]
*
store
.
MemoRelationMessage
,
0
)
for
_
,
memoRelation
:=
range
patchMemoRequest
.
RelationList
{
patchMemoRelationList
=
append
(
patchMemoRelationList
,
&
store
.
MemoRelationMessage
{
MemoID
:
memo
Message
.
ID
,
RelatedMemoID
:
memoRelation
.
RelatedMemoID
,
Type
:
store
.
MemoRelationType
(
memoRelation
.
Type
)
,
})
}
addedMemoRelationList
,
removedMemoRelationList
:=
getMemoRelationListDiff
(
memo
.
RelationList
,
patchMemoRelationList
)
addedMemoRelationList
,
removedMemoRelationList
:=
getMemoRelationListDiff
(
memo
Message
.
RelationList
,
patchMemoRelationList
)
for
_
,
memoRelation
:=
range
addedMemoRelationList
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoRelation
(
ctx
,
&
store
.
MemoRelationMessage
{
MemoID
:
memo
.
ID
,
RelatedMemoID
:
memoRelation
.
RelatedMemoID
,
Type
:
store
.
MemoRelationType
(
memoRelation
.
Type
),
});
err
!=
nil
{
if
_
,
err
:=
s
.
Store
.
UpsertMemoRelation
(
ctx
,
memoRelation
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo relation"
)
.
SetInternal
(
err
)
}
}
for
_
,
memoRelation
:=
range
removedMemoRelationList
{
memoRelationType
:=
store
.
MemoRelationType
(
memoRelation
.
Type
)
if
err
:=
s
.
Store
.
DeleteMemoRelation
(
ctx
,
&
store
.
DeleteMemoRelationMessage
{
MemoID
:
&
memo
.
ID
,
MemoID
:
&
memo
Message
.
ID
,
RelatedMemoID
:
&
memoRelation
.
RelatedMemoID
,
Type
:
&
memoRelationType
,
Type
:
&
memoRelation
.
Type
,
});
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to delete memo relation"
)
.
SetInternal
(
err
)
}
}
}
// After patching memo resources and relations, we need to re-compose it to get the latest data.
memo
,
err
=
s
.
Store
.
ComposeMemo
(
ctx
,
memo
)
memoMessage
,
err
=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoID
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find memo"
)
.
SetInternal
(
err
)
}
memoResponse
,
err
:=
s
.
ComposeMemoResponse
(
ctx
,
convertMemoMessageToMemoResponse
(
memoMessage
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memo
))
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memo
Response
))
})
g
.
GET
(
"/memo"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
memoFind
:=
&
api
.
MemoFind
{}
findMemoMessage
:=
&
store
.
FindMemoMessage
{}
if
userID
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"creatorId"
));
err
==
nil
{
memoFind
.
CreatorID
=
&
userID
findMemoMessage
.
CreatorID
=
&
userID
}
currentUserID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
if
memoFind
.
CreatorID
==
nil
{
if
findMemoMessage
.
CreatorID
==
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Missing user id to find memo"
)
}
memoFind
.
VisibilityList
=
[]
api
.
Visibility
{
api
.
Public
}
findMemoMessage
.
VisibilityList
=
[]
store
.
Visibility
{
store
.
Public
}
}
else
{
if
memoFind
.
CreatorID
==
nil
{
memoFind
.
CreatorID
=
&
currentUserID
if
findMemoMessage
.
CreatorID
==
nil
{
findMemoMessage
.
CreatorID
=
&
currentUserID
}
else
{
memoFind
.
VisibilityList
=
[]
api
.
Visibility
{
api
.
Public
,
api
.
Protected
}
findMemoMessage
.
VisibilityList
=
[]
store
.
Visibility
{
store
.
Public
,
store
.
Protected
}
}
}
rowStatus
:=
api
.
RowStatus
(
c
.
QueryParam
(
"rowStatus"
))
rowStatus
:=
store
.
RowStatus
(
c
.
QueryParam
(
"rowStatus"
))
if
rowStatus
!=
""
{
memoFind
.
RowStatus
=
&
rowStatus
findMemoMessage
.
RowStatus
=
&
rowStatus
}
pinnedStr
:=
c
.
QueryParam
(
"pinned"
)
if
pinnedStr
!=
""
{
pinned
:=
pinnedStr
==
"true"
memoFind
.
Pinned
=
&
pinned
findMemoMessage
.
Pinned
=
&
pinned
}
tag
:=
c
.
QueryParam
(
"tag"
)
if
tag
!=
""
{
contentSearch
:=
"#"
+
tag
memoFind
.
ContentSearch
=
&
contentSearch
findMemoMessage
.
ContentSearch
=
&
contentSearch
}
visibilityListStr
:=
c
.
QueryParam
(
"visibility"
)
if
visibilityListStr
!=
""
{
visibilityList
:=
[]
api
.
Visibility
{}
visibilityList
:=
[]
store
.
Visibility
{}
for
_
,
visibility
:=
range
strings
.
Split
(
visibilityListStr
,
","
)
{
visibilityList
=
append
(
visibilityList
,
api
.
Visibility
(
visibility
))
visibilityList
=
append
(
visibilityList
,
store
.
Visibility
(
visibility
))
}
memoFind
.
VisibilityList
=
visibilityList
findMemoMessage
.
VisibilityList
=
visibilityList
}
if
limit
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"limit"
));
err
==
nil
{
memoFind
.
Limit
=
&
limit
findMemoMessage
.
Limit
=
&
limit
}
if
offset
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"offset"
));
err
==
nil
{
memoFind
.
Offset
=
&
offset
findMemoMessage
.
Offset
=
&
offset
}
list
,
err
:=
s
.
Store
.
FindMemoList
(
ctx
,
memoFind
)
memoMessageList
,
err
:=
s
.
Store
.
ListMemos
(
ctx
,
findMemoMessage
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to fetch memo list"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
list
))
memoResponseList
:=
[]
*
api
.
MemoResponse
{}
for
_
,
memoMessage
:=
range
memoMessageList
{
memoResponse
,
err
:=
s
.
ComposeMemoResponse
(
ctx
,
convertMemoMessageToMemoResponse
(
memoMessage
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
memoResponseList
=
append
(
memoResponseList
,
memoResponse
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memoResponseList
))
})
g
.
GET
(
"/memo/:memoId"
,
func
(
c
echo
.
Context
)
error
{
...
...
@@ -290,29 +314,31 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"ID is not a number: %s"
,
c
.
Param
(
"memoId"
)))
.
SetInternal
(
err
)
}
memo
Find
:=
&
api
.
MemoFind
{
memo
Message
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoID
,
}
memo
,
err
:=
s
.
Store
.
FindMemo
(
ctx
,
memoFind
)
})
if
err
!=
nil
{
if
common
.
ErrorCode
(
err
)
==
common
.
NotFound
{
return
echo
.
NewHTTPError
(
http
.
StatusNotFound
,
fmt
.
Sprintf
(
"Memo ID not found: %d"
,
memoID
))
.
SetInternal
(
err
)
}
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Failed to find memo by ID: %v"
,
memoID
))
.
SetInternal
(
err
)
}
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
memo
.
Visibility
==
api
.
Private
{
if
!
ok
||
memo
.
CreatorID
!=
userID
{
if
memo
Message
.
Visibility
==
store
.
Private
{
if
!
ok
||
memo
Message
.
CreatorID
!=
userID
{
return
echo
.
NewHTTPError
(
http
.
StatusForbidden
,
"this memo is private only"
)
}
}
else
if
memo
.
Visibility
==
api
.
Protected
{
}
else
if
memo
Message
.
Visibility
==
store
.
Protected
{
if
!
ok
{
return
echo
.
NewHTTPError
(
http
.
StatusForbidden
,
"this memo is protected, missing user in session"
)
}
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memo
))
memoResponse
,
err
:=
s
.
ComposeMemoResponse
(
ctx
,
convertMemoMessageToMemoResponse
(
memoMessage
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memoResponse
))
})
g
.
POST
(
"/memo/:memoId/organizer"
,
func
(
c
echo
.
Context
)
error
{
...
...
@@ -338,46 +364,49 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo organizer"
)
.
SetInternal
(
err
)
}
memo
,
err
:=
s
.
Store
.
FindMemo
(
ctx
,
&
api
.
MemoFind
{
memo
Message
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoID
,
})
if
err
!=
nil
{
if
common
.
ErrorCode
(
err
)
==
common
.
NotFound
{
return
echo
.
NewHTTPError
(
http
.
StatusNotFound
,
fmt
.
Sprintf
(
"Memo ID not found: %d"
,
memoID
))
.
SetInternal
(
err
)
}
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
fmt
.
Sprintf
(
"Failed to find memo by ID: %v"
,
memoID
))
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memo
))
memoResponse
,
err
:=
s
.
ComposeMemoResponse
(
ctx
,
convertMemoMessageToMemoResponse
(
memoMessage
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memoResponse
))
})
g
.
GET
(
"/memo/stats"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
normalStatus
:=
api
.
Normal
memoFind
:=
&
api
.
MemoFind
{
normalStatus
:=
store
.
Normal
findMemoMessage
:=
&
store
.
FindMemoMessage
{
RowStatus
:
&
normalStatus
,
}
if
creatorID
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"creatorId"
));
err
==
nil
{
memoFind
.
CreatorID
=
&
creatorID
findMemoMessage
.
CreatorID
=
&
creatorID
}
if
memoFind
.
CreatorID
==
nil
{
if
findMemoMessage
.
CreatorID
==
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Missing user id to find memo"
)
}
currentUserID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
memoFind
.
VisibilityList
=
[]
api
.
Visibility
{
api
.
Public
}
findMemoMessage
.
VisibilityList
=
[]
store
.
Visibility
{
store
.
Public
}
}
else
{
if
*
memoFind
.
CreatorID
!=
currentUserID
{
memoFind
.
VisibilityList
=
[]
api
.
Visibility
{
api
.
Public
,
api
.
Protected
}
if
*
findMemoMessage
.
CreatorID
!=
currentUserID
{
findMemoMessage
.
VisibilityList
=
[]
store
.
Visibility
{
store
.
Public
,
store
.
Protected
}
}
else
{
memoFind
.
VisibilityList
=
[]
api
.
Visibility
{
api
.
Public
,
api
.
Protected
,
api
.
Private
}
findMemoMessage
.
VisibilityList
=
[]
store
.
Visibility
{
store
.
Public
,
store
.
Protected
,
store
.
Private
}
}
}
list
,
err
:=
s
.
Store
.
FindMemoList
(
ctx
,
memoFind
)
list
,
err
:=
s
.
Store
.
ListMemos
(
ctx
,
findMemoMessage
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to f
etch
memo list"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to f
ind
memo list"
)
.
SetInternal
(
err
)
}
createdTsList
:=
[]
int64
{}
...
...
@@ -389,49 +418,56 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
g
.
GET
(
"/memo/all"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
memoFind
:=
&
api
.
MemoFind
{}
findMemoMessage
:=
&
store
.
FindMemoMessage
{}
_
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
memoFind
.
VisibilityList
=
[]
api
.
Visibility
{
api
.
Public
}
findMemoMessage
.
VisibilityList
=
[]
store
.
Visibility
{
store
.
Public
}
}
else
{
memoFind
.
VisibilityList
=
[]
api
.
Visibility
{
api
.
Public
,
api
.
Protected
}
findMemoMessage
.
VisibilityList
=
[]
store
.
Visibility
{
store
.
Public
,
store
.
Protected
}
}
pinnedStr
:=
c
.
QueryParam
(
"pinned"
)
if
pinnedStr
!=
""
{
pinned
:=
pinnedStr
==
"true"
memoFind
.
Pinned
=
&
pinned
findMemoMessage
.
Pinned
=
&
pinned
}
tag
:=
c
.
QueryParam
(
"tag"
)
if
tag
!=
""
{
contentSearch
:=
"#"
+
tag
+
" "
memoFind
.
ContentSearch
=
&
contentSearch
findMemoMessage
.
ContentSearch
=
&
contentSearch
}
visibilityListStr
:=
c
.
QueryParam
(
"visibility"
)
if
visibilityListStr
!=
""
{
visibilityList
:=
[]
api
.
Visibility
{}
visibilityList
:=
[]
store
.
Visibility
{}
for
_
,
visibility
:=
range
strings
.
Split
(
visibilityListStr
,
","
)
{
visibilityList
=
append
(
visibilityList
,
api
.
Visibility
(
visibility
))
visibilityList
=
append
(
visibilityList
,
store
.
Visibility
(
visibility
))
}
memoFind
.
VisibilityList
=
visibilityList
findMemoMessage
.
VisibilityList
=
visibilityList
}
if
limit
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"limit"
));
err
==
nil
{
memoFind
.
Limit
=
&
limit
findMemoMessage
.
Limit
=
&
limit
}
if
offset
,
err
:=
strconv
.
Atoi
(
c
.
QueryParam
(
"offset"
));
err
==
nil
{
memoFind
.
Offset
=
&
offset
findMemoMessage
.
Offset
=
&
offset
}
// Only fetch normal status memos.
normalStatus
:=
api
.
Normal
memoFind
.
RowStatus
=
&
normalStatus
normalStatus
:=
store
.
Normal
findMemoMessage
.
RowStatus
=
&
normalStatus
list
,
err
:=
s
.
Store
.
FindMemoList
(
ctx
,
memoFind
)
memoMessageList
,
err
:=
s
.
Store
.
ListMemos
(
ctx
,
findMemoMessage
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to fetch all memo list"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
list
))
memoResponseList
:=
[]
*
api
.
MemoResponse
{}
for
_
,
memoMessage
:=
range
memoMessageList
{
memoResponse
,
err
:=
s
.
ComposeMemoResponse
(
ctx
,
convertMemoMessageToMemoResponse
(
memoMessage
))
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
memoResponseList
=
append
(
memoResponseList
,
memoResponse
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
memoResponseList
))
})
g
.
DELETE
(
"/memo/:memoId"
,
func
(
c
echo
.
Context
)
error
{
...
...
@@ -445,7 +481,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"ID is not a number: %s"
,
c
.
Param
(
"memoId"
)))
.
SetInternal
(
err
)
}
memo
,
err
:=
s
.
Store
.
FindMemo
(
ctx
,
&
api
.
MemoFind
{
memo
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoID
,
})
if
err
!=
nil
{
...
...
@@ -455,10 +491,9 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Unauthorized"
)
}
memoDelete
:=
&
api
.
MemoDelet
e
{
if
err
:=
s
.
Store
.
DeleteMemo
(
ctx
,
&
store
.
DeleteMemoMessag
e
{
ID
:
memoID
,
}
if
err
:=
s
.
Store
.
DeleteMemo
(
ctx
,
memoDelete
);
err
!=
nil
{
});
err
!=
nil
{
if
common
.
ErrorCode
(
err
)
==
common
.
NotFound
{
return
echo
.
NewHTTPError
(
http
.
StatusNotFound
,
fmt
.
Sprintf
(
"Memo ID not found: %d"
,
memoID
))
}
...
...
@@ -468,7 +503,7 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
})
}
func
(
s
*
Server
)
createMemoCreateActivity
(
c
echo
.
Context
,
memo
*
api
.
Memo
)
error
{
func
(
s
*
Server
)
createMemoCreateActivity
(
c
echo
.
Context
,
memo
*
store
.
MemoMessage
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
payload
:=
api
.
ActivityMemoCreatePayload
{
Content
:
memo
.
Content
,
...
...
@@ -512,7 +547,7 @@ func getIDListDiff(oldList, newList []int) (addedList, removedList []int) {
return
addedList
,
removedList
}
func
getMemoRelationListDiff
(
oldList
,
newList
[]
*
api
.
MemoRelation
)
(
addedList
,
removedList
[]
*
api
.
MemoRelation
)
{
func
getMemoRelationListDiff
(
oldList
,
newList
[]
*
store
.
MemoRelationMessage
)
(
addedList
,
removedList
[]
*
store
.
MemoRelationMessage
)
{
oldMap
:=
map
[
string
]
bool
{}
for
_
,
relation
:=
range
oldList
{
oldMap
[
fmt
.
Sprintf
(
"%d-%s"
,
relation
.
RelatedMemoID
,
relation
.
Type
)]
=
true
...
...
@@ -535,3 +570,64 @@ func getMemoRelationListDiff(oldList, newList []*api.MemoRelation) (addedList, r
}
return
addedList
,
removedList
}
func
convertCreateMemoRequestToMemoMessage
(
memoCreate
*
api
.
CreateMemoRequest
)
*
store
.
MemoMessage
{
createdTs
:=
time
.
Now
()
.
Unix
()
if
memoCreate
.
CreatedTs
!=
nil
{
createdTs
=
*
memoCreate
.
CreatedTs
}
return
&
store
.
MemoMessage
{
CreatorID
:
memoCreate
.
CreatorID
,
CreatedTs
:
createdTs
,
Content
:
memoCreate
.
Content
,
Visibility
:
store
.
Visibility
(
memoCreate
.
Visibility
),
}
}
func
convertMemoMessageToMemoResponse
(
memoMessage
*
store
.
MemoMessage
)
*
api
.
MemoResponse
{
relationList
:=
[]
*
api
.
MemoRelation
{}
for
_
,
relation
:=
range
memoMessage
.
RelationList
{
relationList
=
append
(
relationList
,
convertMemoRelationMessageToMemoRelation
(
relation
))
}
return
&
api
.
MemoResponse
{
ID
:
memoMessage
.
ID
,
RowStatus
:
api
.
RowStatus
(
memoMessage
.
RowStatus
.
String
()),
CreatorID
:
memoMessage
.
CreatorID
,
CreatedTs
:
memoMessage
.
CreatedTs
,
UpdatedTs
:
memoMessage
.
UpdatedTs
,
Content
:
memoMessage
.
Content
,
Visibility
:
api
.
Visibility
(
memoMessage
.
Visibility
.
String
()),
Pinned
:
memoMessage
.
Pinned
,
ResourceIDList
:
memoMessage
.
ResourceIDList
,
RelationList
:
relationList
,
}
}
func
(
s
*
Server
)
ComposeMemoResponse
(
ctx
context
.
Context
,
memoResponse
*
api
.
MemoResponse
)
(
*
api
.
MemoResponse
,
error
)
{
user
,
err
:=
s
.
Store
.
FindUser
(
ctx
,
&
api
.
UserFind
{
ID
:
&
memoResponse
.
CreatorID
,
})
if
err
!=
nil
{
return
nil
,
err
}
if
user
.
Nickname
!=
""
{
memoResponse
.
CreatorName
=
user
.
Nickname
}
else
{
memoResponse
.
CreatorName
=
user
.
Username
}
memoResponse
.
ResourceList
=
[]
*
api
.
Resource
{}
for
_
,
resourceID
:=
range
memoResponse
.
ResourceIDList
{
resource
,
err
:=
s
.
Store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
ID
:
&
resourceID
,
})
if
err
!=
nil
{
return
nil
,
err
}
memoResponse
.
ResourceList
=
append
(
memoResponse
.
ResourceList
,
resource
)
}
return
memoResponse
,
nil
}
server/memo_relation.go
View file @
ea728d23
...
...
@@ -74,3 +74,11 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) {
return
c
.
JSON
(
http
.
StatusOK
,
true
)
})
}
func
convertMemoRelationMessageToMemoRelation
(
memoRelation
*
store
.
MemoRelationMessage
)
*
api
.
MemoRelation
{
return
&
api
.
MemoRelation
{
MemoID
:
memoRelation
.
MemoID
,
RelatedMemoID
:
memoRelation
.
RelatedMemoID
,
Type
:
api
.
MemoRelationType
(
memoRelation
.
Type
),
}
}
server/memo_resource.go
View file @
ea728d23
...
...
@@ -8,6 +8,7 @@ import (
"time"
"github.com/usememos/memos/api"
"github.com/usememos/memos/store"
"github.com/labstack/echo/v4"
)
...
...
@@ -82,7 +83,7 @@ func (s *Server) registerMemoResourceRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"Resource ID is not a number: %s"
,
c
.
Param
(
"resourceId"
)))
.
SetInternal
(
err
)
}
memo
,
err
:=
s
.
Store
.
FindMemo
(
ctx
,
&
api
.
MemoFind
{
memo
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memoID
,
})
if
err
!=
nil
{
...
...
server/rss.go
View file @
ea728d23
...
...
@@ -13,6 +13,7 @@ import (
"github.com/labstack/echo/v4"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
"github.com/usememos/memos/store"
"github.com/yuin/goldmark"
)
...
...
@@ -24,12 +25,12 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to get system customized profile"
)
.
SetInternal
(
err
)
}
normalStatus
:=
api
.
Normal
memoFind
:=
api
.
MemoFind
{
normalStatus
:=
store
.
Normal
memoFind
:=
store
.
FindMemoMessage
{
RowStatus
:
&
normalStatus
,
VisibilityList
:
[]
api
.
Visibility
{
api
.
Public
},
VisibilityList
:
[]
store
.
Visibility
{
store
.
Public
},
}
memoList
,
err
:=
s
.
Store
.
FindMemoList
(
ctx
,
&
memoFind
)
memoList
,
err
:=
s
.
Store
.
ListMemos
(
ctx
,
&
memoFind
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find memo list"
)
.
SetInternal
(
err
)
}
...
...
@@ -55,13 +56,13 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to get system customized profile"
)
.
SetInternal
(
err
)
}
normalStatus
:=
api
.
Normal
memoFind
:=
api
.
MemoFind
{
normalStatus
:=
store
.
Normal
memoFind
:=
store
.
FindMemoMessage
{
CreatorID
:
&
id
,
RowStatus
:
&
normalStatus
,
VisibilityList
:
[]
api
.
Visibility
{
api
.
Public
},
VisibilityList
:
[]
store
.
Visibility
{
store
.
Public
},
}
memoList
,
err
:=
s
.
Store
.
FindMemoList
(
ctx
,
&
memoFind
)
memoList
,
err
:=
s
.
Store
.
ListMemos
(
ctx
,
&
memoFind
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find memo list"
)
.
SetInternal
(
err
)
}
...
...
@@ -79,7 +80,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
const
MaxRSSItemCount
=
100
const
MaxRSSItemTitleLength
=
100
func
(
s
*
Server
)
generateRSSFromMemoList
(
ctx
context
.
Context
,
memoList
[]
*
api
.
Memo
,
baseURL
string
,
profile
*
api
.
CustomizedProfile
)
(
string
,
error
)
{
func
(
s
*
Server
)
generateRSSFromMemoList
(
ctx
context
.
Context
,
memoList
[]
*
store
.
MemoMessage
,
baseURL
string
,
profile
*
api
.
CustomizedProfile
)
(
string
,
error
)
{
feed
:=
&
feeds
.
Feed
{
Title
:
profile
.
Name
,
Link
:
&
feeds
.
Link
{
Href
:
baseURL
},
...
...
@@ -98,15 +99,15 @@ func (s *Server) generateRSSFromMemoList(ctx context.Context, memoList []*api.Me
Created
:
time
.
Unix
(
memo
.
CreatedTs
,
0
),
Enclosure
:
&
feeds
.
Enclosure
{
Url
:
baseURL
+
"/m/"
+
strconv
.
Itoa
(
memo
.
ID
)
+
"/image"
},
}
resourceList
,
err
:=
s
.
Store
.
FindResourceList
(
ctx
,
&
api
.
ResourceFind
{
MemoID
:
&
memo
.
ID
,
})
if
err
!=
nil
{
return
""
,
err
}
if
len
(
resourceList
)
>
0
{
if
len
(
memo
.
ResourceIDList
)
>
0
{
resourceID
:=
memo
.
ResourceIDList
[
0
]
resource
,
err
:=
s
.
Store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
ID
:
&
resourceID
,
})
if
err
!=
nil
{
return
""
,
err
}
enclosure
:=
feeds
.
Enclosure
{}
resource
:=
resourceList
[
0
]
if
resource
.
ExternalLink
!=
""
{
enclosure
.
Url
=
resource
.
ExternalLink
}
else
{
...
...
server/tag.go
View file @
ea728d23
...
...
@@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
"github.com/usememos/memos/store"
"golang.org/x/exp/slices"
"github.com/labstack/echo/v4"
...
...
@@ -71,14 +72,14 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Missing user session"
)
}
contentSearch
:=
"#"
normalRowStatus
:=
api
.
Normal
memoFind
:=
api
.
MemoFind
{
normalRowStatus
:=
store
.
Normal
memoFind
:=
&
store
.
FindMemoMessage
{
CreatorID
:
&
userID
,
ContentSearch
:
&
contentSearch
,
RowStatus
:
&
normalRowStatus
,
}
memo
List
,
err
:=
s
.
Store
.
FindMemoList
(
ctx
,
&
memoFind
)
memo
MessageList
,
err
:=
s
.
Store
.
ListMemos
(
ctx
,
memoFind
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find memo list"
)
.
SetInternal
(
err
)
}
...
...
@@ -96,7 +97,7 @@ func (s *Server) registerTagRoutes(g *echo.Group) {
}
tagMapSet
:=
make
(
map
[
string
]
bool
)
for
_
,
memo
:=
range
memoList
{
for
_
,
memo
:=
range
memo
Message
List
{
for
_
,
tag
:=
range
findTagListFromMemoContent
(
memo
.
Content
)
{
if
!
slices
.
Contains
(
tagNameList
,
tag
)
{
tagMapSet
[
tag
]
=
true
...
...
store/common.go
0 → 100644
View file @
ea728d23
package
store
// RowStatus is the status for a row.
type
RowStatus
string
const
(
// Normal is the status for a normal row.
Normal
RowStatus
=
"NORMAL"
// Archived is the status for an archived row.
Archived
RowStatus
=
"ARCHIVED"
)
func
(
r
RowStatus
)
String
()
string
{
switch
r
{
case
Normal
:
return
"NORMAL"
case
Archived
:
return
"ARCHIVED"
}
return
""
}
store/memo.go
View file @
ea728d23
...
...
@@ -4,300 +4,221 @@ import (
"context"
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
)
// memoRaw is the store model for an Memo.
// Fields have exactly the same meanings as Memo.
type
memoRaw
struct
{
// Visibility is the type of a visibility.
type
Visibility
string
const
(
// Public is the PUBLIC visibility.
Public
Visibility
=
"PUBLIC"
// Protected is the PROTECTED visibility.
Protected
Visibility
=
"PROTECTED"
// Private is the PRIVATE visibility.
Private
Visibility
=
"PRIVATE"
)
func
(
v
Visibility
)
String
()
string
{
switch
v
{
case
Public
:
return
"PUBLIC"
case
Protected
:
return
"PROTECTED"
case
Private
:
return
"PRIVATE"
}
return
"PRIVATE"
}
type
MemoMessage
struct
{
ID
int
// Standard fields
RowStatus
api
.
RowStatus
RowStatus
RowStatus
CreatorID
int
CreatedTs
int64
UpdatedTs
int64
// Domain specific fields
Content
string
Visibility
api
.
Visibility
Pinned
bool
}
Visibility
Visibility
// toMemo creates an instance of Memo based on the memoRaw.
// This is intended to be called when we need to compose an Memo relationship.
func
(
raw
*
memoRaw
)
toMemo
()
*
api
.
Memo
{
return
&
api
.
Memo
{
ID
:
raw
.
ID
,
// Standard fields
RowStatus
:
raw
.
RowStatus
,
CreatorID
:
raw
.
CreatorID
,
CreatedTs
:
raw
.
CreatedTs
,
UpdatedTs
:
raw
.
UpdatedTs
,
// Domain specific fields
Content
:
raw
.
Content
,
Visibility
:
raw
.
Visibility
,
Pinned
:
raw
.
Pinned
,
ResourceList
:
[]
*
api
.
Resource
{},
RelationList
:
[]
*
api
.
MemoRelation
{},
}
// Composed fields
Pinned
bool
ResourceIDList
[]
int
RelationList
[]
*
MemoRelationMessage
}
func
(
s
*
Store
)
ComposeMemo
(
ctx
context
.
Context
,
memo
*
api
.
Memo
)
(
*
api
.
Memo
,
error
)
{
if
err
:=
s
.
ComposeMemoCreator
(
ctx
,
memo
);
err
!=
nil
{
return
nil
,
err
}
if
err
:=
s
.
ComposeMemoResourceList
(
ctx
,
memo
);
err
!=
nil
{
return
nil
,
err
}
if
err
:=
s
.
ComposeMemoRelationList
(
ctx
,
memo
);
err
!=
nil
{
return
nil
,
err
}
return
memo
,
nil
}
type
FindMemoMessage
struct
{
ID
*
int
func
(
s
*
Store
)
CreateMemo
(
ctx
context
.
Context
,
create
*
api
.
MemoCreate
)
(
*
api
.
Memo
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
tx
.
Rollback
()
// Standard fields
RowStatus
*
RowStatus
CreatorID
*
int
memoRaw
,
err
:=
createMemoRaw
(
ctx
,
tx
,
create
)
if
err
!=
nil
{
return
nil
,
err
}
// Domain specific fields
Pinned
*
bool
ContentSearch
*
string
VisibilityList
[]
Visibility
if
err
:=
tx
.
Commit
();
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
// Pagination
Limit
*
int
Offset
*
int
}
s
.
memoCache
.
Store
(
memoRaw
.
ID
,
memoRaw
)
memo
,
err
:=
s
.
ComposeMemo
(
ctx
,
memoRaw
.
toMemo
())
if
err
!=
nil
{
return
nil
,
err
}
type
UpdateMemoMessage
struct
{
ID
int
CreatedTs
*
int64
UpdatedTs
*
int64
RowStatus
*
RowStatus
Content
*
string
Visibility
*
Visibility
}
return
memo
,
nil
type
DeleteMemoMessage
struct
{
ID
int
}
func
(
s
*
Store
)
PatchMemo
(
ctx
context
.
Context
,
patch
*
api
.
MemoPatch
)
(
*
api
.
Memo
,
error
)
{
func
(
s
*
Store
)
CreateMemo
(
ctx
context
.
Context
,
create
*
MemoMessage
)
(
*
MemoMessage
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
tx
.
Rollback
()
memoRaw
,
err
:=
patchMemoRaw
(
ctx
,
tx
,
patch
)
if
err
!=
nil
{
return
nil
,
err
query
:=
`
INSERT INTO memo (
creator_id,
created_ts,
content,
visibility
)
VALUES (?, ?, ?, ?)
RETURNING id, created_ts, updated_ts, row_status
`
if
err
:=
tx
.
QueryRowContext
(
ctx
,
query
,
create
.
CreatorID
,
create
.
CreatedTs
,
create
.
Content
,
create
.
Visibility
,
)
.
Scan
(
&
create
.
ID
,
&
create
.
CreatedTs
,
&
create
.
UpdatedTs
,
&
create
.
RowStatus
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
if
err
:=
tx
.
Commit
();
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
s
.
memoCache
.
Store
(
memoRaw
.
ID
,
memoRaw
)
memo
,
err
:=
s
.
ComposeMemo
(
ctx
,
memoRaw
.
toMemo
())
if
err
!=
nil
{
return
nil
,
err
}
return
memo
,
nil
memoMessage
:=
create
return
memoMessage
,
nil
}
func
(
s
*
Store
)
FindMemoList
(
ctx
context
.
Context
,
find
*
api
.
MemoFind
)
([]
*
api
.
Memo
,
error
)
{
func
(
s
*
Store
)
ListMemos
(
ctx
context
.
Context
,
find
*
FindMemoMessage
)
([]
*
MemoMessage
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
tx
.
Rollback
()
memoRawList
,
err
:=
findMemoRawList
(
ctx
,
tx
,
find
)
list
,
err
:=
listMemos
(
ctx
,
tx
,
find
)
if
err
!=
nil
{
return
nil
,
err
}
list
:=
[]
*
api
.
Memo
{}
for
_
,
raw
:=
range
memoRawList
{
memo
,
err
:=
s
.
ComposeMemo
(
ctx
,
raw
.
toMemo
())
if
err
!=
nil
{
return
nil
,
err
}
list
=
append
(
list
,
memo
)
}
return
list
,
nil
}
func
(
s
*
Store
)
FindMemo
(
ctx
context
.
Context
,
find
*
api
.
MemoFind
)
(
*
api
.
Memo
,
error
)
{
if
find
.
ID
!=
nil
{
if
memo
,
ok
:=
s
.
memoCache
.
Load
(
*
find
.
ID
);
ok
{
memoRaw
:=
memo
.
(
*
memoRaw
)
memo
,
err
:=
s
.
ComposeMemo
(
ctx
,
memoRaw
.
toMemo
())
if
err
!=
nil
{
return
nil
,
err
}
return
memo
,
nil
}
}
func
(
s
*
Store
)
GetMemo
(
ctx
context
.
Context
,
find
*
FindMemoMessage
)
(
*
MemoMessage
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
tx
.
Rollback
()
list
,
err
:=
findMemoRawList
(
ctx
,
tx
,
find
)
list
,
err
:=
listMemos
(
ctx
,
tx
,
find
)
if
err
!=
nil
{
return
nil
,
err
}
if
len
(
list
)
==
0
{
return
nil
,
&
common
.
Error
{
Code
:
common
.
NotFound
,
Err
:
fmt
.
Errorf
(
"not found"
)}
return
nil
,
&
common
.
Error
{
Code
:
common
.
NotFound
,
Err
:
fmt
.
Errorf
(
"
memo
not found"
)}
}
memoRaw
:=
list
[
0
]
s
.
memoCache
.
Store
(
memoRaw
.
ID
,
memoRaw
)
memo
,
err
:=
s
.
ComposeMemo
(
ctx
,
memoRaw
.
toMemo
())
if
err
!=
nil
{
return
nil
,
err
}
return
memo
,
nil
memoMessage
:=
list
[
0
]
return
memoMessage
,
nil
}
func
(
s
*
Store
)
DeleteMemo
(
ctx
context
.
Context
,
delete
*
api
.
MemoDelet
e
)
error
{
func
(
s
*
Store
)
UpdateMemo
(
ctx
context
.
Context
,
update
*
UpdateMemoMessag
e
)
error
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
FormatError
(
err
)
}
defer
tx
.
Rollback
()
if
err
:=
deleteMemo
(
ctx
,
tx
,
delete
);
err
!=
nil
{
return
FormatError
(
err
)
}
if
err
:=
s
.
vacuumImpl
(
ctx
,
tx
);
err
!=
nil
{
return
err
}
defer
tx
.
Rollback
()
if
err
:=
tx
.
Commit
();
err
!=
nil
{
return
FormatError
(
err
)
}
s
.
memoCache
.
Delete
(
delete
.
ID
)
return
nil
}
func
createMemoRaw
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
create
*
api
.
MemoCreate
)
(
*
memoRaw
,
error
)
{
set
:=
[]
string
{
"creator_id"
,
"content"
,
"visibility"
}
args
:=
[]
any
{
create
.
CreatorID
,
create
.
Content
,
create
.
Visibility
}
placeholder
:=
[]
string
{
"?"
,
"?"
,
"?"
}
if
v
:=
create
.
CreatedTs
;
v
!=
nil
{
set
,
args
,
placeholder
=
append
(
set
,
"created_ts"
),
append
(
args
,
*
v
),
append
(
placeholder
,
"?"
)
}
query
:=
`
INSERT INTO memo (
`
+
strings
.
Join
(
set
,
", "
)
+
`
)
VALUES (`
+
strings
.
Join
(
placeholder
,
","
)
+
`)
RETURNING id, creator_id, created_ts, updated_ts, row_status, content, visibility
`
var
memoRaw
memoRaw
if
err
:=
tx
.
QueryRowContext
(
ctx
,
query
,
args
...
)
.
Scan
(
&
memoRaw
.
ID
,
&
memoRaw
.
CreatorID
,
&
memoRaw
.
CreatedTs
,
&
memoRaw
.
UpdatedTs
,
&
memoRaw
.
RowStatus
,
&
memoRaw
.
Content
,
&
memoRaw
.
Visibility
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
return
&
memoRaw
,
nil
}
func
patchMemoRaw
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
patch
*
api
.
MemoPatch
)
(
*
memoRaw
,
error
)
{
set
,
args
:=
[]
string
{},
[]
any
{}
if
v
:=
patch
.
CreatedTs
;
v
!=
nil
{
if
v
:=
update
.
CreatedTs
;
v
!=
nil
{
set
,
args
=
append
(
set
,
"created_ts = ?"
),
append
(
args
,
*
v
)
}
if
v
:=
patch
.
UpdatedTs
;
v
!=
nil
{
if
v
:=
update
.
UpdatedTs
;
v
!=
nil
{
set
,
args
=
append
(
set
,
"updated_ts = ?"
),
append
(
args
,
*
v
)
}
if
v
:=
patch
.
RowStatus
;
v
!=
nil
{
set
,
args
=
append
(
set
,
"row_status = ?"
),
append
(
args
,
*
v
)
}
if
v
:=
patch
.
Content
;
v
!=
nil
{
if
v
:=
update
.
Content
;
v
!=
nil
{
set
,
args
=
append
(
set
,
"content = ?"
),
append
(
args
,
*
v
)
}
if
v
:=
patch
.
Visibility
;
v
!=
nil
{
if
v
:=
update
.
Visibility
;
v
!=
nil
{
set
,
args
=
append
(
set
,
"visibility = ?"
),
append
(
args
,
*
v
)
}
args
=
append
(
args
,
patch
.
ID
)
args
=
append
(
args
,
update
.
ID
)
query
:=
`
UPDATE memo
SET `
+
strings
.
Join
(
set
,
", "
)
+
`
WHERE id = ?
RETURNING id, creator_id, created_ts, updated_ts, row_status, content, visibility
`
var
memoRaw
memoRaw
if
err
:=
tx
.
QueryRowContext
(
ctx
,
query
,
args
...
)
.
Scan
(
&
memoRaw
.
ID
,
&
memoRaw
.
CreatorID
,
&
memoRaw
.
CreatedTs
,
&
memoRaw
.
UpdatedTs
,
&
memoRaw
.
RowStatus
,
&
memoRaw
.
Content
,
&
memoRaw
.
Visibility
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
if
_
,
err
:=
tx
.
ExecContext
(
ctx
,
query
,
args
...
);
err
!=
nil
{
return
err
}
err
=
tx
.
Commit
()
return
err
}
pinnedQuery
:=
`
SELECT
pinned
FROM memo_organizer
WHERE memo_id = ? AND user_id = ?
`
row
,
err
:=
tx
.
QueryContext
(
ctx
,
pinnedQuery
,
patch
.
ID
,
memoRaw
.
CreatorID
)
func
(
s
*
Store
)
DeleteMemo
(
ctx
context
.
Context
,
delete
*
DeleteMemoMessage
)
error
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
return
FormatError
(
err
)
}
defer
row
.
Close
()
defer
tx
.
Rollback
()
if
!
row
.
Next
()
{
memoRaw
.
Pinned
=
false
}
else
{
if
err
:=
row
.
Scan
(
&
memoRaw
.
Pinned
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
where
,
args
:=
[]
string
{
"id = ?"
},
[]
any
{
delete
.
ID
}
stmt
:=
`DELETE FROM memo WHERE `
+
strings
.
Join
(
where
,
" AND "
)
result
,
err
:=
tx
.
ExecContext
(
ctx
,
stmt
,
args
...
)
if
err
!=
nil
{
return
FormatError
(
err
)
}
if
err
:=
row
.
Err
();
err
!=
nil
{
return
nil
,
err
rows
,
err
:=
result
.
RowsAffected
()
if
err
!=
nil
{
return
err
}
return
&
memoRaw
,
nil
if
rows
==
0
{
return
&
common
.
Error
{
Code
:
common
.
NotFound
,
Err
:
fmt
.
Errorf
(
"idp not found"
)}
}
if
err
:=
s
.
vacuumImpl
(
ctx
,
tx
);
err
!=
nil
{
return
err
}
err
=
tx
.
Commit
()
return
err
}
func
findMemoRawList
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
find
*
api
.
MemoFind
)
([]
*
memoRaw
,
error
)
{
func
listMemos
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
find
*
FindMemoMessage
)
([]
*
MemoMessage
,
error
)
{
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
if
v
:=
find
.
ID
;
v
!=
nil
{
...
...
@@ -325,19 +246,35 @@ func findMemoRawList(ctx context.Context, tx *sql.Tx, find *api.MemoFind) ([]*me
}
query
:=
`
SELECT
memo.id,
memo.creator_id,
memo.created_ts,
memo.updated_ts,
memo.row_status,
memo.content,
memo.visibility,
IFNULL(memo_organizer.pinned, 0) AS pinned
FROM memo
LEFT JOIN memo_organizer ON memo_organizer.memo_id = memo.id AND memo_organizer.user_id = memo.creator_id
WHERE `
+
strings
.
Join
(
where
,
" AND "
)
+
`
ORDER BY pinned DESC, memo.created_ts DESC
SELECT
memo.id AS id,
memo.creator_id AS creator_id,
memo.created_ts AS created_ts,
memo.updated_ts AS updated_ts,
memo.row_status AS row_status,
memo.content AS content,
memo.visibility AS visibility,
CASE WHEN memo_organizer.pinned = 1 THEN 1 ELSE 0 END AS pinned,
GROUP_CONCAT(memo_resource.resource_id) AS resource_id_list,
(
SELECT
GROUP_CONCAT(related_memo_id || ':' || type)
FROM
memo_relation
WHERE
memo_relation.memo_id = memo.id
GROUP BY
memo_relation.memo_id
) AS relation_list
FROM
memo
LEFT JOIN
memo_organizer ON memo.id = memo_organizer.memo_id
LEFT JOIN
memo_resource ON memo.id = memo_resource.memo_id
WHERE `
+
strings
.
Join
(
where
,
" AND "
)
+
`
GROUP BY memo.id
ORDER BY pinned DESC, memo.created_ts DESC
`
if
find
.
Limit
!=
nil
{
query
=
fmt
.
Sprintf
(
"%s LIMIT %d"
,
query
,
*
find
.
Limit
)
...
...
@@ -352,51 +289,64 @@ func findMemoRawList(ctx context.Context, tx *sql.Tx, find *api.MemoFind) ([]*me
}
defer
rows
.
Close
()
memo
RawList
:=
make
([]
*
memoRaw
,
0
)
memo
MessageList
:=
make
([]
*
MemoMessage
,
0
)
for
rows
.
Next
()
{
var
memoRaw
memoRaw
var
pinned
sql
.
NullBool
var
memoMessage
MemoMessage
var
memoResourceIDList
sql
.
NullString
var
memoRelationList
sql
.
NullString
if
err
:=
rows
.
Scan
(
&
memoRaw
.
ID
,
&
memoRaw
.
CreatorID
,
&
memoRaw
.
CreatedTs
,
&
memoRaw
.
UpdatedTs
,
&
memoRaw
.
RowStatus
,
&
memoRaw
.
Content
,
&
memoRaw
.
Visibility
,
&
pinned
,
&
memoMessage
.
ID
,
&
memoMessage
.
CreatorID
,
&
memoMessage
.
CreatedTs
,
&
memoMessage
.
UpdatedTs
,
&
memoMessage
.
RowStatus
,
&
memoMessage
.
Content
,
&
memoMessage
.
Visibility
,
&
memoMessage
.
Pinned
,
&
memoResourceIDList
,
&
memoRelationList
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
if
pinned
.
Valid
{
memoRaw
.
Pinned
=
pinned
.
Bool
if
memoResourceIDList
.
Valid
{
idStringList
:=
strings
.
Split
(
memoResourceIDList
.
String
,
","
)
memoMessage
.
ResourceIDList
=
make
([]
int
,
0
,
len
(
idStringList
))
for
_
,
idString
:=
range
idStringList
{
id
,
err
:=
strconv
.
Atoi
(
idString
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
memoMessage
.
ResourceIDList
=
append
(
memoMessage
.
ResourceIDList
,
id
)
}
}
if
memoRelationList
.
Valid
{
memoMessage
.
RelationList
=
make
([]
*
MemoRelationMessage
,
0
)
relatedMemoTypeList
:=
strings
.
Split
(
memoRelationList
.
String
,
","
)
for
_
,
relatedMemoType
:=
range
relatedMemoTypeList
{
relatedMemoTypeList
:=
strings
.
Split
(
relatedMemoType
,
":"
)
if
len
(
relatedMemoTypeList
)
!=
2
{
return
nil
,
&
common
.
Error
{
Code
:
common
.
Invalid
,
Err
:
fmt
.
Errorf
(
"invalid relation format"
)}
}
relatedMemoID
,
err
:=
strconv
.
Atoi
(
relatedMemoTypeList
[
0
])
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
memoMessage
.
RelationList
=
append
(
memoMessage
.
RelationList
,
&
MemoRelationMessage
{
MemoID
:
memoMessage
.
ID
,
RelatedMemoID
:
relatedMemoID
,
Type
:
MemoRelationType
(
relatedMemoTypeList
[
1
]),
})
}
}
memo
RawList
=
append
(
memoRawList
,
&
memoRaw
)
memo
MessageList
=
append
(
memoMessageList
,
&
memoMessage
)
}
if
err
:=
rows
.
Err
();
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
return
memoRawList
,
nil
}
func
deleteMemo
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
delete
*
api
.
MemoDelete
)
error
{
where
,
args
:=
[]
string
{
"id = ?"
},
[]
any
{
delete
.
ID
}
stmt
:=
`DELETE FROM memo WHERE `
+
strings
.
Join
(
where
,
" AND "
)
result
,
err
:=
tx
.
ExecContext
(
ctx
,
stmt
,
args
...
)
if
err
!=
nil
{
return
FormatError
(
err
)
}
rows
,
_
:=
result
.
RowsAffected
()
if
rows
==
0
{
return
&
common
.
Error
{
Code
:
common
.
NotFound
,
Err
:
fmt
.
Errorf
(
"memo not found"
)}
}
return
nil
return
memoMessageList
,
nil
}
func
vacuumMemo
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
)
error
{
...
...
store/memo_relation.go
View file @
ea728d23
...
...
@@ -6,30 +6,9 @@ import (
"fmt"
"strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
)
func
(
s
*
Store
)
ComposeMemoRelationList
(
ctx
context
.
Context
,
memo
*
api
.
Memo
)
error
{
memoRelationList
,
err
:=
s
.
ListMemoRelations
(
ctx
,
&
FindMemoRelationMessage
{
MemoID
:
&
memo
.
ID
,
})
if
err
!=
nil
{
return
err
}
memo
.
RelationList
=
[]
*
api
.
MemoRelation
{}
for
_
,
memoRelation
:=
range
memoRelationList
{
memo
.
RelationList
=
append
(
memo
.
RelationList
,
&
api
.
MemoRelation
{
MemoID
:
memoRelation
.
MemoID
,
RelatedMemoID
:
memoRelation
.
RelatedMemoID
,
Type
:
api
.
MemoRelationType
(
memoRelation
.
Type
),
})
}
return
nil
}
type
MemoRelationType
string
const
(
...
...
store/resource.go
View file @
ea728d23
...
...
@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"fmt"
"sort"
"strings"
"github.com/usememos/memos/api"
...
...
@@ -53,40 +52,6 @@ func (raw *resourceRaw) toResource() *api.Resource {
}
}
func
(
s
*
Store
)
ComposeMemoResourceList
(
ctx
context
.
Context
,
memo
*
api
.
Memo
)
error
{
resourceList
,
err
:=
s
.
FindResourceList
(
ctx
,
&
api
.
ResourceFind
{
MemoID
:
&
memo
.
ID
,
})
if
err
!=
nil
{
return
err
}
for
_
,
resource
:=
range
resourceList
{
memoResource
,
err
:=
s
.
FindMemoResource
(
ctx
,
&
api
.
MemoResourceFind
{
MemoID
:
&
memo
.
ID
,
ResourceID
:
&
resource
.
ID
,
})
if
err
!=
nil
{
return
err
}
resource
.
CreatedTs
=
memoResource
.
CreatedTs
resource
.
UpdatedTs
=
memoResource
.
UpdatedTs
}
sort
.
Slice
(
resourceList
,
func
(
i
,
j
int
)
bool
{
if
resourceList
[
i
]
.
CreatedTs
!=
resourceList
[
j
]
.
CreatedTs
{
return
resourceList
[
i
]
.
CreatedTs
<
resourceList
[
j
]
.
CreatedTs
}
return
resourceList
[
i
]
.
ID
<
resourceList
[
j
]
.
ID
})
memo
.
ResourceList
=
resourceList
return
nil
}
func
(
s
*
Store
)
CreateResource
(
ctx
context
.
Context
,
create
*
api
.
ResourceCreate
)
(
*
api
.
Resource
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
...
...
store/store.go
View file @
ea728d23
...
...
@@ -15,7 +15,6 @@ type Store struct {
systemSettingCache
sync
.
Map
// map[string]*systemSettingRaw
userCache
sync
.
Map
// map[int]*userRaw
userSettingCache
sync
.
Map
// map[string]*userSettingRaw
memoCache
sync
.
Map
// map[int]*memoRaw
shortcutCache
sync
.
Map
// map[int]*shortcutRaw
idpCache
sync
.
Map
// map[int]*identityProviderMessage
}
...
...
store/user.go
View file @
ea728d23
...
...
@@ -11,34 +11,20 @@ import (
)
func
(
s
*
Store
)
SeedDataForNewUser
(
ctx
context
.
Context
,
user
*
api
.
User
)
error
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
FormatError
(
err
)
}
defer
tx
.
Rollback
()
// Create a memo for the user.
_
,
err
=
createMemoRaw
(
ctx
,
tx
,
&
api
.
MemoCreat
e
{
_
,
err
:=
s
.
CreateMemo
(
ctx
,
&
MemoMessag
e
{
CreatorID
:
user
.
ID
,
Content
:
"#inbox Welcome to Memos!"
,
Visibility
:
api
.
Private
,
Visibility
:
Private
,
})
if
err
!=
nil
{
return
err
}
_
,
err
=
upsertTag
(
ctx
,
tx
,
&
api
.
TagUpsert
{
_
,
err
=
s
.
UpsertTag
(
c
tx
,
&
api
.
TagUpsert
{
CreatorID
:
user
.
ID
,
Name
:
"inbox"
,
})
if
err
!=
nil
{
return
err
}
if
err
:=
tx
.
Commit
();
err
!=
nil
{
return
FormatError
(
err
)
}
return
nil
return
err
}
// userRaw is the store model for an User.
...
...
@@ -79,22 +65,6 @@ func (raw *userRaw) toUser() *api.User {
}
}
func
(
s
*
Store
)
ComposeMemoCreator
(
ctx
context
.
Context
,
memo
*
api
.
Memo
)
error
{
user
,
err
:=
s
.
FindUser
(
ctx
,
&
api
.
UserFind
{
ID
:
&
memo
.
CreatorID
,
})
if
err
!=
nil
{
return
err
}
if
user
.
Nickname
!=
""
{
memo
.
CreatorName
=
user
.
Nickname
}
else
{
memo
.
CreatorName
=
user
.
Username
}
return
nil
}
func
(
s
*
Store
)
CreateUser
(
ctx
context
.
Context
,
create
*
api
.
UserCreate
)
(
*
api
.
User
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
...
...
test/server/memo_relation_test.go
View file @
ea728d23
...
...
@@ -28,12 +28,12 @@ func TestMemoRelationServer(t *testing.T) {
memoList
,
err
:=
s
.
getMemoList
()
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
memoList
,
1
)
memo
,
err
:=
s
.
postMemoCreate
(
&
api
.
MemoCreate
{
memo
,
err
:=
s
.
postMemoCreate
(
&
api
.
CreateMemoRequest
{
Content
:
"test memo"
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
"test memo"
,
memo
.
Content
)
memo2
,
err
:=
s
.
postMemoCreate
(
&
api
.
MemoCreate
{
memo2
,
err
:=
s
.
postMemoCreate
(
&
api
.
CreateMemoRequest
{
Content
:
"test memo2"
,
RelationList
:
[]
*
api
.
MemoRelationUpsert
{
{
...
...
test/server/memo_test.go
View file @
ea728d23
...
...
@@ -28,7 +28,7 @@ func TestMemoServer(t *testing.T) {
memoList
,
err
:=
s
.
getMemoList
()
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
memoList
,
1
)
memo
,
err
:=
s
.
postMemoCreate
(
&
api
.
MemoCreate
{
memo
,
err
:=
s
.
postMemoCreate
(
&
api
.
CreateMemoRequest
{
Content
:
"test memo"
,
})
require
.
NoError
(
t
,
err
)
...
...
@@ -37,7 +37,7 @@ func TestMemoServer(t *testing.T) {
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
memoList
,
2
)
updatedContent
:=
"updated memo"
memo
,
err
=
s
.
patchMemo
(
&
api
.
MemoPatch
{
memo
,
err
=
s
.
patchMemo
(
&
api
.
PatchMemoRequest
{
ID
:
memo
.
ID
,
Content
:
&
updatedContent
,
})
...
...
@@ -50,23 +50,21 @@ func TestMemoServer(t *testing.T) {
Pinned
:
true
,
})
require
.
NoError
(
t
,
err
)
memo
,
err
=
s
.
patchMemo
(
&
api
.
MemoPatch
{
memo
,
err
=
s
.
patchMemo
(
&
api
.
PatchMemoRequest
{
ID
:
memo
.
ID
,
Content
:
&
updatedContent
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
updatedContent
,
memo
.
Content
)
require
.
Equal
(
t
,
true
,
memo
.
Pinned
)
err
=
s
.
deleteMemo
(
&
api
.
MemoDelete
{
ID
:
memo
.
ID
,
})
err
=
s
.
deleteMemo
(
memo
.
ID
)
require
.
NoError
(
t
,
err
)
memoList
,
err
=
s
.
getMemoList
()
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
memoList
,
1
)
}
func
(
s
*
TestingServer
)
getMemo
(
memoID
int
)
(
*
api
.
Memo
,
error
)
{
func
(
s
*
TestingServer
)
getMemo
(
memoID
int
)
(
*
api
.
Memo
Response
,
error
)
{
body
,
err
:=
s
.
get
(
fmt
.
Sprintf
(
"/api/memo/%d"
,
memoID
),
nil
)
if
err
!=
nil
{
return
nil
,
err
...
...
@@ -79,7 +77,7 @@ func (s *TestingServer) getMemo(memoID int) (*api.Memo, error) {
}
type
MemoCreateResponse
struct
{
Data
*
api
.
Memo
`json:"data"`
Data
*
api
.
Memo
Response
`json:"data"`
}
res
:=
new
(
MemoCreateResponse
)
if
err
=
json
.
Unmarshal
(
buf
.
Bytes
(),
res
);
err
!=
nil
{
...
...
@@ -88,7 +86,7 @@ func (s *TestingServer) getMemo(memoID int) (*api.Memo, error) {
return
res
.
Data
,
nil
}
func
(
s
*
TestingServer
)
getMemoList
()
([]
*
api
.
Memo
,
error
)
{
func
(
s
*
TestingServer
)
getMemoList
()
([]
*
api
.
Memo
Response
,
error
)
{
body
,
err
:=
s
.
get
(
"/api/memo"
,
nil
)
if
err
!=
nil
{
return
nil
,
err
...
...
@@ -101,7 +99,7 @@ func (s *TestingServer) getMemoList() ([]*api.Memo, error) {
}
type
MemoCreateResponse
struct
{
Data
[]
*
api
.
Memo
`json:"data"`
Data
[]
*
api
.
Memo
Response
`json:"data"`
}
res
:=
new
(
MemoCreateResponse
)
if
err
=
json
.
Unmarshal
(
buf
.
Bytes
(),
res
);
err
!=
nil
{
...
...
@@ -110,7 +108,7 @@ func (s *TestingServer) getMemoList() ([]*api.Memo, error) {
return
res
.
Data
,
nil
}
func
(
s
*
TestingServer
)
postMemoCreate
(
memoCreate
*
api
.
MemoCreate
)
(
*
api
.
Memo
,
error
)
{
func
(
s
*
TestingServer
)
postMemoCreate
(
memoCreate
*
api
.
CreateMemoRequest
)
(
*
api
.
MemoResponse
,
error
)
{
rawData
,
err
:=
json
.
Marshal
(
&
memoCreate
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal memo create"
)
...
...
@@ -128,7 +126,7 @@ func (s *TestingServer) postMemoCreate(memoCreate *api.MemoCreate) (*api.Memo, e
}
type
MemoCreateResponse
struct
{
Data
*
api
.
Memo
`json:"data"`
Data
*
api
.
Memo
Response
`json:"data"`
}
res
:=
new
(
MemoCreateResponse
)
if
err
=
json
.
Unmarshal
(
buf
.
Bytes
(),
res
);
err
!=
nil
{
...
...
@@ -137,7 +135,7 @@ func (s *TestingServer) postMemoCreate(memoCreate *api.MemoCreate) (*api.Memo, e
return
res
.
Data
,
nil
}
func
(
s
*
TestingServer
)
patchMemo
(
memoPatch
*
api
.
MemoPatch
)
(
*
api
.
Memo
,
error
)
{
func
(
s
*
TestingServer
)
patchMemo
(
memoPatch
*
api
.
PatchMemoRequest
)
(
*
api
.
MemoResponse
,
error
)
{
rawData
,
err
:=
json
.
Marshal
(
&
memoPatch
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal memo patch"
)
...
...
@@ -155,7 +153,7 @@ func (s *TestingServer) patchMemo(memoPatch *api.MemoPatch) (*api.Memo, error) {
}
type
MemoPatchResponse
struct
{
Data
*
api
.
Memo
`json:"data"`
Data
*
api
.
Memo
Response
`json:"data"`
}
res
:=
new
(
MemoPatchResponse
)
if
err
=
json
.
Unmarshal
(
buf
.
Bytes
(),
res
);
err
!=
nil
{
...
...
@@ -164,12 +162,12 @@ func (s *TestingServer) patchMemo(memoPatch *api.MemoPatch) (*api.Memo, error) {
return
res
.
Data
,
nil
}
func
(
s
*
TestingServer
)
deleteMemo
(
memo
Delete
*
api
.
MemoDelete
)
error
{
_
,
err
:=
s
.
delete
(
fmt
.
Sprintf
(
"/api/memo/%d"
,
memo
Delete
.
ID
),
nil
)
func
(
s
*
TestingServer
)
deleteMemo
(
memo
ID
int
)
error
{
_
,
err
:=
s
.
delete
(
fmt
.
Sprintf
(
"/api/memo/%d"
,
memoID
),
nil
)
return
err
}
func
(
s
*
TestingServer
)
postMemosOrganizer
(
memosOrganizer
*
api
.
MemoOrganizerUpsert
)
(
*
api
.
Memo
,
error
)
{
func
(
s
*
TestingServer
)
postMemosOrganizer
(
memosOrganizer
*
api
.
MemoOrganizerUpsert
)
(
*
api
.
Memo
Response
,
error
)
{
rawData
,
err
:=
json
.
Marshal
(
&
memosOrganizer
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal memos organizer"
)
...
...
@@ -187,7 +185,7 @@ func (s *TestingServer) postMemosOrganizer(memosOrganizer *api.MemoOrganizerUpse
}
type
MemoOrganizerResponse
struct
{
Data
*
api
.
Memo
`json:"data"`
Data
*
api
.
Memo
Response
`json:"data"`
}
res
:=
new
(
MemoOrganizerResponse
)
if
err
=
json
.
Unmarshal
(
buf
.
Bytes
(),
res
);
err
!=
nil
{
...
...
test/store/memo_relation_test.go
View file @
ea728d23
...
...
@@ -5,7 +5,6 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/api"
"github.com/usememos/memos/store"
)
...
...
@@ -14,22 +13,22 @@ func TestMemoRelationStore(t *testing.T) {
ts
:=
NewTestingStore
(
ctx
,
t
)
user
,
err
:=
createTestingHostUser
(
ctx
,
ts
)
require
.
NoError
(
t
,
err
)
memoCreate
:=
&
api
.
MemoCreat
e
{
memoCreate
:=
&
store
.
MemoMessag
e
{
CreatorID
:
user
.
ID
,
Content
:
"test_content"
,
Visibility
:
api
.
Public
,
Visibility
:
store
.
Public
,
}
memo
,
err
:=
ts
.
CreateMemo
(
ctx
,
memoCreate
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
memoCreate
.
Content
,
memo
.
Content
)
memo
Create
=
&
api
.
MemoCreat
e
{
memo
2Create
:=
&
store
.
MemoMessag
e
{
CreatorID
:
user
.
ID
,
Content
:
"test_content_2"
,
Visibility
:
api
.
Public
,
Visibility
:
store
.
Public
,
}
memo2
,
err
:=
ts
.
CreateMemo
(
ctx
,
memoCreate
)
memo2
,
err
:=
ts
.
CreateMemo
(
ctx
,
memo
2
Create
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
memoCreate
.
Content
,
memo2
.
Content
)
require
.
Equal
(
t
,
memo
2
Create
.
Content
,
memo2
.
Content
)
memoRelationMessage
:=
&
store
.
MemoRelationMessage
{
MemoID
:
memo
.
ID
,
RelatedMemoID
:
memo2
.
ID
,
...
...
@@ -45,7 +44,7 @@ func TestMemoRelationStore(t *testing.T) {
require
.
Equal
(
t
,
memo2
.
ID
,
memoRelation
[
0
]
.
RelatedMemoID
)
require
.
Equal
(
t
,
memo
.
ID
,
memoRelation
[
0
]
.
MemoID
)
require
.
Equal
(
t
,
store
.
MemoRelationReference
,
memoRelation
[
0
]
.
Type
)
err
=
ts
.
DeleteMemo
(
ctx
,
&
api
.
MemoDelet
e
{
err
=
ts
.
DeleteMemo
(
ctx
,
&
store
.
DeleteMemoMessag
e
{
ID
:
memo2
.
ID
,
})
require
.
NoError
(
t
,
err
)
...
...
test/store/memo_test.go
View file @
ea728d23
...
...
@@ -5,37 +5,40 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/
api
"
"github.com/usememos/memos/
store
"
)
func
TestMemoStore
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
store
:=
NewTestingStore
(
ctx
,
t
)
user
,
err
:=
createTestingHostUser
(
ctx
,
store
)
ts
:=
NewTestingStore
(
ctx
,
t
)
user
,
err
:=
createTestingHostUser
(
ctx
,
ts
)
require
.
NoError
(
t
,
err
)
memoCreate
:=
&
api
.
MemoCreat
e
{
memoCreate
:=
&
store
.
MemoMessag
e
{
CreatorID
:
user
.
ID
,
Content
:
"test_content"
,
Visibility
:
api
.
Public
,
Visibility
:
store
.
Public
,
}
memo
,
err
:=
store
.
CreateMemo
(
ctx
,
memoCreate
)
memo
,
err
:=
ts
.
CreateMemo
(
ctx
,
memoCreate
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
memoCreate
.
Content
,
memo
.
Content
)
memoPatchContent
:=
"test_content_2"
memoPatch
:=
&
api
.
MemoPatch
{
memoPatch
:=
&
store
.
UpdateMemoMessage
{
ID
:
memo
.
ID
,
Content
:
&
memoPatchContent
,
}
memo
,
err
=
store
.
Patch
Memo
(
ctx
,
memoPatch
)
err
=
ts
.
Update
Memo
(
ctx
,
memoPatch
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
memoPatchContent
,
memo
.
Content
)
memoList
,
err
:=
store
.
FindMemoList
(
ctx
,
&
api
.
MemoFind
{
memo
,
err
=
ts
.
GetMemo
(
ctx
,
&
store
.
FindMemoMessage
{
ID
:
&
memo
.
ID
,
})
require
.
NoError
(
t
,
err
)
memoList
,
err
:=
ts
.
ListMemos
(
ctx
,
&
store
.
FindMemoMessage
{
CreatorID
:
&
user
.
ID
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
2
,
len
(
memoList
))
require
.
Equal
(
t
,
memo
,
memoList
[
1
])
err
=
store
.
DeleteMemo
(
ctx
,
&
api
.
MemoDelet
e
{
err
=
ts
.
DeleteMemo
(
ctx
,
&
store
.
DeleteMemoMessag
e
{
ID
:
memo
.
ID
,
})
require
.
NoError
(
t
,
err
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment