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
1fa9f162
Unverified
Commit
1fa9f162
authored
Jul 05, 2023
by
boojack
Committed by
GitHub
Jul 05, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: migrate resource to apiv1 (#1901)
parent
5ea561af
Changes
15
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
419 additions
and
339 deletions
+419
-339
resource.go
api/resource.go
+0
-48
memo_resource.go
api/v1/memo_resource.go
+24
-0
resource.go
api/v1/resource.go
+137
-75
v1.go
api/v1/v1.go
+7
-0
memo.go
server/memo.go
+21
-2
memo_resource.go
server/memo_resource.go
+9
-7
rss.go
server/rss.go
+1
-2
server.go
server/server.go
+0
-2
telegram.go
server/telegram.go
+2
-3
memo_resource.go
store/memo_resource.go
+79
-0
resource.go
store/resource.go
+110
-172
store.go
store/store.go
+0
-1
resource_test.go
test/store/resource_test.go
+14
-12
api.ts
web/src/helpers/api.ts
+9
-9
resource.ts
web/src/store/module/resource.ts
+6
-6
No files found.
api/resource.go
View file @
1fa9f162
...
...
@@ -20,51 +20,3 @@ type Resource struct {
// Related fields
LinkedMemoAmount
int
`json:"linkedMemoAmount"`
}
type
ResourceCreate
struct
{
// Standard fields
CreatorID
int
`json:"-"`
// Domain specific fields
Filename
string
`json:"filename"`
Blob
[]
byte
`json:"-"`
InternalPath
string
`json:"internalPath"`
ExternalLink
string
`json:"externalLink"`
Type
string
`json:"type"`
Size
int64
`json:"-"`
PublicID
string
`json:"publicId"`
DownloadToLocal
bool
`json:"downloadToLocal"`
}
type
ResourceFind
struct
{
ID
*
int
`json:"id"`
// Standard fields
CreatorID
*
int
`json:"creatorId"`
// Domain specific fields
Filename
*
string
`json:"filename"`
MemoID
*
int
PublicID
*
string
`json:"publicId"`
GetBlob
bool
// Pagination
Limit
*
int
Offset
*
int
}
type
ResourcePatch
struct
{
ID
int
`json:"-"`
// Standard fields
UpdatedTs
*
int64
// Domain specific fields
Filename
*
string
`json:"filename"`
ResetPublicID
*
bool
`json:"resetPublicId"`
PublicID
*
string
`json:"-"`
}
type
ResourceDelete
struct
{
ID
int
}
api/v1/memo_resource.go
0 → 100644
View file @
1fa9f162
package
v1
type
MemoResource
struct
{
MemoID
int
ResourceID
int
CreatedTs
int64
UpdatedTs
int64
}
type
MemoResourceUpsert
struct
{
MemoID
int
`json:"-"`
ResourceID
int
UpdatedTs
*
int64
}
type
MemoResourceFind
struct
{
MemoID
*
int
ResourceID
*
int
}
type
MemoResourceDelete
struct
{
MemoID
*
int
ResourceID
*
int
}
server
/resource.go
→
api/v1
/resource.go
View file @
1fa9f162
This diff is collapsed.
Click to expand it.
api/v1/v1.go
View file @
1fa9f162
...
...
@@ -34,4 +34,11 @@ func (s *APIV1Service) Register(rootGroup *echo.Group) {
s
.
registerTagRoutes
(
apiV1Group
)
s
.
registerShortcutRoutes
(
apiV1Group
)
s
.
registerStorageRoutes
(
apiV1Group
)
s
.
registerResourceRoutes
(
apiV1Group
)
publicGroup
:=
rootGroup
.
Group
(
"/o"
)
publicGroup
.
Use
(
func
(
next
echo
.
HandlerFunc
)
echo
.
HandlerFunc
{
return
JWTMiddleware
(
s
,
next
,
s
.
Secret
)
})
s
.
registerResourcePublicRoutes
(
publicGroup
)
}
server/memo.go
View file @
1fa9f162
...
...
@@ -685,13 +685,15 @@ func (s *Server) composeMemoMessageToMemoResponse(ctx context.Context, memoMessa
resourceList
:=
[]
*
api
.
Resource
{}
for
_
,
resourceID
:=
range
memoMessage
.
ResourceIDList
{
resource
,
err
:=
s
.
Store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
ID
:
&
resourceID
,
})
if
err
!=
nil
{
return
nil
,
err
}
resourceList
=
append
(
resourceList
,
resource
)
if
resource
!=
nil
{
resourceList
=
append
(
resourceList
,
convertResourceFromStore
(
resource
))
}
}
memoResponse
.
ResourceList
=
resourceList
...
...
@@ -714,3 +716,20 @@ func (s *Server) getMemoDisplayWithUpdatedTsSettingValue(ctx context.Context) (b
}
return
memoDisplayWithUpdatedTs
,
nil
}
func
convertResourceFromStore
(
resource
*
store
.
Resource
)
*
api
.
Resource
{
return
&
api
.
Resource
{
ID
:
resource
.
ID
,
CreatorID
:
resource
.
CreatorID
,
CreatedTs
:
resource
.
CreatedTs
,
UpdatedTs
:
resource
.
UpdatedTs
,
Filename
:
resource
.
Filename
,
Blob
:
resource
.
Blob
,
InternalPath
:
resource
.
InternalPath
,
ExternalLink
:
resource
.
ExternalLink
,
Type
:
resource
.
Type
,
Size
:
resource
.
Size
,
PublicID
:
resource
.
PublicID
,
LinkedMemoAmount
:
resource
.
LinkedMemoAmount
,
}
}
server/memo_resource.go
View file @
1fa9f162
...
...
@@ -29,10 +29,9 @@ func (s *Server) registerMemoResourceRoutes(g *echo.Group) {
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
memoResourceUpsert
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted post memo resource request"
)
.
SetInternal
(
err
)
}
resource
Find
:=
&
api
.
ResourceFind
{
resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
ID
:
&
memoResourceUpsert
.
ResourceID
,
}
resource
,
err
:=
s
.
Store
.
FindResource
(
ctx
,
resourceFind
)
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to fetch resource"
)
.
SetInternal
(
err
)
}
...
...
@@ -48,7 +47,7 @@ func (s *Server) registerMemoResourceRoutes(g *echo.Group) {
if
_
,
err
:=
s
.
Store
.
UpsertMemoResource
(
ctx
,
memoResourceUpsert
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert memo resource"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
resource
)
)
return
c
.
JSON
(
http
.
StatusOK
,
true
)
})
g
.
GET
(
"/memo/:memoId/resource"
,
func
(
c
echo
.
Context
)
error
{
...
...
@@ -58,13 +57,16 @@ func (s *Server) registerMemoResourceRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
fmt
.
Sprintf
(
"ID is not a number: %s"
,
c
.
Param
(
"memoId"
)))
.
SetInternal
(
err
)
}
resourceFind
:=
&
api
.
ResourceFind
{
list
,
err
:=
s
.
Store
.
ListResources
(
ctx
,
&
store
.
FindResource
{
MemoID
:
&
memoID
,
}
resourceList
,
err
:=
s
.
Store
.
FindResourceList
(
ctx
,
resourceFind
)
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to fetch resource list"
)
.
SetInternal
(
err
)
}
resourceList
:=
[]
*
api
.
Resource
{}
for
_
,
resource
:=
range
list
{
resourceList
=
append
(
resourceList
,
convertResourceFromStore
(
resource
))
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
resourceList
))
})
...
...
server/rss.go
View file @
1fa9f162
...
...
@@ -11,7 +11,6 @@ import (
"github.com/gorilla/feeds"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/api"
apiv1
"github.com/usememos/memos/api/v1"
"github.com/usememos/memos/common"
"github.com/usememos/memos/store"
...
...
@@ -102,7 +101,7 @@ func (s *Server) generateRSSFromMemoList(ctx context.Context, memoList []*store.
}
if
len
(
memo
.
ResourceIDList
)
>
0
{
resourceID
:=
memo
.
ResourceIDList
[
0
]
resource
,
err
:=
s
.
Store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
ID
:
&
resourceID
,
})
if
err
!=
nil
{
...
...
server/server.go
View file @
1fa9f162
...
...
@@ -92,7 +92,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
return
JWTMiddleware
(
s
,
next
,
s
.
Secret
)
})
registerGetterPublicRoutes
(
publicGroup
)
s
.
registerResourcePublicRoutes
(
publicGroup
)
apiGroup
:=
e
.
Group
(
"/api"
)
apiGroup
.
Use
(
func
(
next
echo
.
HandlerFunc
)
echo
.
HandlerFunc
{
...
...
@@ -100,7 +99,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
})
s
.
registerMemoRoutes
(
apiGroup
)
s
.
registerMemoResourceRoutes
(
apiGroup
)
s
.
registerResourceRoutes
(
apiGroup
)
s
.
registerMemoRelationRoutes
(
apiGroup
)
apiV1Service
:=
apiv1
.
NewAPIV1Service
(
s
.
Secret
,
profile
,
store
)
...
...
server/telegram.go
View file @
1fa9f162
...
...
@@ -90,15 +90,14 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
case
".png"
:
mime
=
"image/png"
}
resource
Create
:=
api
.
ResourceCreat
e
{
resource
,
err
:=
t
.
store
.
CreateResourceV1
(
ctx
,
&
store
.
Resourc
e
{
CreatorID
:
creatorID
,
Filename
:
filename
,
Type
:
mime
,
Size
:
int64
(
len
(
blob
)),
Blob
:
blob
,
PublicID
:
common
.
GenUUID
(),
}
resource
,
err
:=
t
.
store
.
CreateResource
(
ctx
,
&
resourceCreate
)
})
if
err
!=
nil
{
_
,
err
:=
bot
.
EditMessage
(
ctx
,
message
.
Chat
.
ID
,
reply
.
MessageID
,
fmt
.
Sprintf
(
"failed to CreateResource: %s"
,
err
),
nil
)
return
err
...
...
store/memo_resource.go
View file @
1fa9f162
...
...
@@ -10,6 +10,85 @@ import (
"github.com/usememos/memos/common"
)
type
MemoResource
struct
{
MemoID
int
ResourceID
int
CreatedTs
int64
UpdatedTs
int64
}
type
FindMemoResource
struct
{
MemoID
*
int
ResourceID
*
int
}
func
(
s
*
Store
)
ListMemoResources
(
ctx
context
.
Context
,
find
*
FindMemoResource
)
([]
*
MemoResource
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
err
}
defer
tx
.
Rollback
()
list
,
err
:=
listMemoResources
(
ctx
,
tx
,
find
)
if
err
!=
nil
{
return
nil
,
err
}
if
err
:=
tx
.
Commit
();
err
!=
nil
{
return
nil
,
err
}
return
list
,
nil
}
func
listMemoResources
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
find
*
FindMemoResource
)
([]
*
MemoResource
,
error
)
{
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
if
v
:=
find
.
MemoID
;
v
!=
nil
{
where
,
args
=
append
(
where
,
"memo_id = ?"
),
append
(
args
,
*
v
)
}
if
v
:=
find
.
ResourceID
;
v
!=
nil
{
where
,
args
=
append
(
where
,
"resource_id = ?"
),
append
(
args
,
*
v
)
}
query
:=
`
SELECT
memo_id,
resource_id,
created_ts,
updated_ts
FROM memo_resource
WHERE `
+
strings
.
Join
(
where
,
" AND "
)
+
`
ORDER BY updated_ts DESC
`
rows
,
err
:=
tx
.
QueryContext
(
ctx
,
query
,
args
...
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
rows
.
Close
()
list
:=
make
([]
*
MemoResource
,
0
)
for
rows
.
Next
()
{
var
memoResource
MemoResource
if
err
:=
rows
.
Scan
(
&
memoResource
.
MemoID
,
&
memoResource
.
ResourceID
,
&
memoResource
.
CreatedTs
,
&
memoResource
.
UpdatedTs
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
list
=
append
(
list
,
&
memoResource
)
}
if
err
:=
rows
.
Err
();
err
!=
nil
{
return
nil
,
err
}
return
list
,
nil
}
// memoResourceRaw is the store model for an MemoResource.
// Fields have exactly the same meanings as MemoResource.
type
memoResourceRaw
struct
{
...
...
store/resource.go
View file @
1fa9f162
This diff is collapsed.
Click to expand it.
store/store.go
View file @
1fa9f162
...
...
@@ -17,7 +17,6 @@ type Store struct {
userSettingCache
sync
.
Map
// map[string]*UserSetting
shortcutCache
sync
.
Map
// map[int]*shortcutRaw
idpCache
sync
.
Map
// map[int]*IdentityProvider
resourceCache
sync
.
Map
// map[int]*resourceRaw
}
// New creates a new instance of Store.
...
...
test/store/resource_test.go
View file @
1fa9f162
...
...
@@ -5,13 +5,13 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/
api
"
"github.com/usememos/memos/
store
"
)
func
TestResourceStore
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
store
:=
NewTestingStore
(
ctx
,
t
)
_
,
err
:=
store
.
CreateResource
(
ctx
,
&
api
.
ResourceCreat
e
{
ts
:=
NewTestingStore
(
ctx
,
t
)
_
,
err
:=
ts
.
CreateResourceV1
(
ctx
,
&
store
.
Resourc
e
{
CreatorID
:
101
,
Filename
:
"test.epub"
,
Blob
:
[]
byte
(
"test"
),
...
...
@@ -25,34 +25,36 @@ func TestResourceStore(t *testing.T) {
correctFilename
:=
"test.epub"
incorrectFilename
:=
"test.png"
res
,
err
:=
store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
res
,
err
:=
ts
.
GetResource
(
ctx
,
&
store
.
FindResource
{
Filename
:
&
correctFilename
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
correctFilename
,
res
.
Filename
)
require
.
Equal
(
t
,
1
,
res
.
ID
)
_
,
err
=
store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
notFoundResource
,
err
:=
ts
.
GetResource
(
ctx
,
&
store
.
FindResource
{
Filename
:
&
incorrectFilename
,
})
require
.
Error
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
notFoundResource
)
correctCreatorID
:=
101
incorrectCreatorID
:=
102
_
,
err
=
store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
_
,
err
=
ts
.
GetResource
(
ctx
,
&
store
.
FindResource
{
CreatorID
:
&
correctCreatorID
,
})
require
.
NoError
(
t
,
err
)
_
,
err
=
store
.
FindResource
(
ctx
,
&
api
.
ResourceFind
{
notFoundResource
,
err
=
ts
.
GetResource
(
ctx
,
&
store
.
FindResource
{
CreatorID
:
&
incorrectCreatorID
,
})
require
.
Error
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
notFoundResource
)
err
=
store
.
DeleteResource
(
ctx
,
&
api
.
ResourceDelet
e
{
err
=
ts
.
DeleteResourceV1
(
ctx
,
&
store
.
DeleteResourc
e
{
ID
:
1
,
})
require
.
NoError
(
t
,
err
)
err
=
store
.
DeleteResource
(
ctx
,
&
api
.
ResourceDelet
e
{
err
=
ts
.
DeleteResourceV1
(
ctx
,
&
store
.
DeleteResourc
e
{
ID
:
2
,
})
require
.
Error
(
t
,
err
)
require
.
No
Error
(
t
,
err
)
}
web/src/helpers/api.ts
View file @
1fa9f162
...
...
@@ -161,7 +161,7 @@ export function deleteShortcutById(shortcutId: ShortcutId) {
}
export
function
getResourceList
()
{
return
axios
.
get
<
Res
ponseObject
<
Resource
[]
>>
(
"/api
/resource"
);
return
axios
.
get
<
Res
ource
[]
>
(
"/api/v1
/resource"
);
}
export
function
getResourceListWithLimit
(
resourceFind
?:
ResourceFind
)
{
...
...
@@ -172,23 +172,23 @@ export function getResourceListWithLimit(resourceFind?: ResourceFind) {
if
(
resourceFind
?.
limit
)
{
queryList
.
push
(
`limit=
${
resourceFind
.
limit
}
`
);
}
return
axios
.
get
<
Res
ponseObject
<
Resource
[]
>>
(
`/api
/resource?
${
queryList
.
join
(
"&"
)}
`
);
return
axios
.
get
<
Res
ource
[]
>
(
`/api/v1
/resource?
${
queryList
.
join
(
"&"
)}
`
);
}
export
function
createResource
(
resourceCreate
:
ResourceCreate
)
{
return
axios
.
post
<
Res
ponseObject
<
Resource
>>
(
"/api
/resource"
,
resourceCreate
);
return
axios
.
post
<
Res
ource
>
(
"/api/v1
/resource"
,
resourceCreate
);
}
export
function
createResourceWithBlob
(
formData
:
FormData
)
{
return
axios
.
post
<
Res
ponseObject
<
Resource
>>
(
"/api
/resource/blob"
,
formData
);
return
axios
.
post
<
Res
ource
>
(
"/api/v1
/resource/blob"
,
formData
);
}
export
function
deleteResourceById
(
id
:
ResourceId
)
{
return
axios
.
delete
(
`/api/resource/
${
id
}
`
);
export
function
patchResource
(
resourcePatch
:
ResourcePatch
)
{
return
axios
.
patch
<
Resource
>
(
`/api/v1/resource/
${
resourcePatch
.
id
}
`
,
resourcePatch
);
}
export
function
patchResource
(
resourcePatch
:
ResourcePatch
)
{
return
axios
.
patch
<
ResponseObject
<
Resource
>>
(
`/api/resource/
${
resourcePatch
.
id
}
`
,
resourcePatch
);
export
function
deleteResourceById
(
id
:
ResourceId
)
{
return
axios
.
delete
(
`/api/v1/resource/
${
id
}
`
);
}
export
function
getMemoResourceList
(
memoId
:
MemoId
)
{
...
...
@@ -196,7 +196,7 @@ export function getMemoResourceList(memoId: MemoId) {
}
export
function
upsertMemoResource
(
memoId
:
MemoId
,
resourceId
:
ResourceId
)
{
return
axios
.
post
<
ResponseObject
<
Resource
>>
(
`/api/memo/
${
memoId
}
/resource`
,
{
return
axios
.
post
(
`/api/memo/
${
memoId
}
/resource`
,
{
resourceId
,
});
}
...
...
web/src/store/module/resource.ts
View file @
1fa9f162
...
...
@@ -25,7 +25,7 @@ export const useResourceStore = () => {
return
store
.
getState
().
resource
;
},
async
fetchResourceList
():
Promise
<
Resource
[]
>
{
const
{
data
}
=
(
await
api
.
getResourceList
()).
data
;
const
{
data
}
=
await
api
.
getResourceList
()
;
const
resourceList
=
data
.
map
((
m
)
=>
convertResponseModelResource
(
m
));
store
.
dispatch
(
setResources
(
resourceList
));
return
resourceList
;
...
...
@@ -35,13 +35,13 @@ export const useResourceStore = () => {
limit
,
offset
,
};
const
{
data
}
=
(
await
api
.
getResourceListWithLimit
(
resourceFind
)).
data
;
const
{
data
}
=
await
api
.
getResourceListWithLimit
(
resourceFind
)
;
const
resourceList
=
data
.
map
((
m
)
=>
convertResponseModelResource
(
m
));
store
.
dispatch
(
upsertResources
(
resourceList
));
return
resourceList
;
},
async
createResource
(
resourceCreate
:
ResourceCreate
):
Promise
<
Resource
>
{
const
{
data
}
=
(
await
api
.
createResource
(
resourceCreate
)).
data
;
const
{
data
}
=
await
api
.
createResource
(
resourceCreate
)
;
const
resource
=
convertResponseModelResource
(
data
);
const
resourceList
=
state
.
resources
;
store
.
dispatch
(
setResources
([
resource
,
...
resourceList
]));
...
...
@@ -55,7 +55,7 @@ export const useResourceStore = () => {
const
formData
=
new
FormData
();
formData
.
append
(
"file"
,
file
,
filename
);
const
{
data
}
=
(
await
api
.
createResourceWithBlob
(
formData
)).
data
;
const
{
data
}
=
await
api
.
createResourceWithBlob
(
formData
)
;
const
resource
=
convertResponseModelResource
(
data
);
const
resourceList
=
state
.
resources
;
store
.
dispatch
(
setResources
([
resource
,
...
resourceList
]));
...
...
@@ -71,7 +71,7 @@ export const useResourceStore = () => {
const
formData
=
new
FormData
();
formData
.
append
(
"file"
,
file
,
filename
);
const
{
data
}
=
(
await
api
.
createResourceWithBlob
(
formData
)).
data
;
const
{
data
}
=
await
api
.
createResourceWithBlob
(
formData
)
;
const
resource
=
convertResponseModelResource
(
data
);
newResourceList
=
[
resource
,
...
newResourceList
];
}
...
...
@@ -84,7 +84,7 @@ export const useResourceStore = () => {
store
.
dispatch
(
deleteResource
(
id
));
},
async
patchResource
(
resourcePatch
:
ResourcePatch
):
Promise
<
Resource
>
{
const
{
data
}
=
(
await
api
.
patchResource
(
resourcePatch
)).
data
;
const
{
data
}
=
await
api
.
patchResource
(
resourcePatch
)
;
const
resource
=
convertResponseModelResource
(
data
);
store
.
dispatch
(
patchResource
(
resource
));
return
resource
;
...
...
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