From 2ca226b0bcf55a7113d9696f0feab48d09241b11 Mon Sep 17 00:00:00 2001
From: Noah Alderton <noahlouisalderton@gmail.com>
Date: Sun, 14 Jan 2024 02:28:13 -0800
Subject: [PATCH 1/5] Created endpoint to create .zip of all memos

---
 api/v1/memo.go | 169 +++++++++++++++++++++++++++++--------------------
 1 file changed, 102 insertions(+), 67 deletions(-)

diff --git a/api/v1/memo.go b/api/v1/memo.go
index 28ddc9f64cc22..0504ecac661b8 100644
--- a/api/v1/memo.go
+++ b/api/v1/memo.go
@@ -1,6 +1,8 @@
 package v1
 
 import (
+	"archive/zip"
+	"bytes"
 	"context"
 	"encoding/json"
 	"fmt"
@@ -120,6 +122,7 @@ const maxContentLength = 1 << 30
 func (s *APIV1Service) registerMemoRoutes(g *echo.Group) {
 	g.GET("/memo", s.GetMemoList)
 	g.POST("/memo", s.CreateMemo)
+	g.GET("/memo/export", s.ExportMemo)
 	g.GET("/memo/all", s.GetAllMemos)
 	g.GET("/memo/stats", s.GetMemoStats)
 	g.GET("/memo/:memoId", s.GetMemo)
@@ -145,84 +148,42 @@ func (s *APIV1Service) registerMemoRoutes(g *echo.Group) {
 //	@Failure	500				{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to compose memo response"
 //	@Router		/api/v1/memo [GET]
 func (s *APIV1Service) GetMemoList(c echo.Context) error {
-	ctx := c.Request().Context()
-	find := &store.FindMemo{
-		OrderByPinned: true,
-	}
-	if userID, err := util.ConvertStringToInt32(c.QueryParam("creatorId")); err == nil {
-		find.CreatorID = &userID
-	}
-
-	if username := c.QueryParam("creatorUsername"); username != "" {
-		user, _ := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
-		if user != nil {
-			find.CreatorID = &user.ID
-		}
+	list, httpError := s.getMemoList(c)
+	if httpError != nil {
+		return httpError
 	}
 
-	currentUserID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		// Anonymous use should only fetch PUBLIC memos with specified user
-		if find.CreatorID == nil {
-			return echo.NewHTTPError(http.StatusBadRequest, "Missing user to find memo")
-		}
-		find.VisibilityList = []store.Visibility{store.Public}
-	} else {
-		// Authorized user can fetch all PUBLIC/PROTECTED memo
-		visibilityList := []store.Visibility{store.Public, store.Protected}
-
-		// If Creator is authorized user (as default), PRIVATE memo is OK
-		if find.CreatorID == nil || *find.CreatorID == currentUserID {
-			find.CreatorID = &currentUserID
-			visibilityList = append(visibilityList, store.Private)
+	memoResponseList := []*Memo{}
+	for _, memo := range list {
+		memoResponse, err := s.convertMemoFromStore(c.Request().Context(), memo)
+		if err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
 		}
-		find.VisibilityList = visibilityList
-	}
-
-	rowStatus := store.RowStatus(c.QueryParam("rowStatus"))
-	if rowStatus != "" {
-		find.RowStatus = &rowStatus
-	}
-
-	contentSearch := []string{}
-	tag := c.QueryParam("tag")
-	if tag != "" {
-		contentSearch = append(contentSearch, "#"+tag)
-	}
-	content := c.QueryParam("content")
-	if content != "" {
-		contentSearch = append(contentSearch, content)
-	}
-	find.ContentSearch = contentSearch
-
-	if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
-		find.Limit = &limit
-	}
-	if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
-		find.Offset = &offset
+		memoResponseList = append(memoResponseList, memoResponse)
 	}
+	return c.JSON(http.StatusOK, memoResponseList)
+}
 
-	memoDisplayWithUpdatedTs, err := s.getMemoDisplayWithUpdatedTsSettingValue(ctx)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get memo display with updated ts setting value").SetInternal(err)
-	}
-	if memoDisplayWithUpdatedTs {
-		find.OrderByUpdatedTs = true
+func (s *APIV1Service) ExportMemo(c echo.Context) error {
+	list, httpError := s.getMemoList(c)
+	if httpError != nil {
+		return httpError
 	}
 
-	list, err := s.Store.ListMemos(ctx, find)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err)
-	}
-	memoResponseList := []*Memo{}
+	buf := new(bytes.Buffer)
+	writer := zip.NewWriter(buf)
 	for _, memo := range list {
-		memoResponse, err := s.convertMemoFromStore(ctx, memo)
+		f, err := writer.Create(time.Unix(memo.CreatedTs, 0).Format(time.RFC3339) + ".md")
 		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create memo file").SetInternal(err)
 		}
-		memoResponseList = append(memoResponseList, memoResponse)
+		_, err = f.Write([]byte(memo.Content))
 	}
-	return c.JSON(http.StatusOK, memoResponseList)
+	err := writer.Close()
+	if err != nil {
+		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to close zip file writer").SetInternal(err)
+	}
+	return c.Blob(http.StatusOK, "application/zip", buf.Bytes())
 }
 
 // CreateMemo godoc
@@ -948,6 +909,80 @@ func getIDListDiff(oldList, newList []int32) (addedList, removedList []int32) {
 	return addedList, removedList
 }
 
+func (s *APIV1Service) getMemoList(c echo.Context) ([]*store.Memo, *echo.HTTPError) {
+	ctx := c.Request().Context()
+	find := &store.FindMemo{
+		OrderByPinned: true,
+	}
+	if userID, err := util.ConvertStringToInt32(c.QueryParam("creatorId")); err == nil {
+		find.CreatorID = &userID
+	}
+
+	if username := c.QueryParam("creatorUsername"); username != "" {
+		user, _ := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
+		if user != nil {
+			find.CreatorID = &user.ID
+		}
+	}
+
+	currentUserID, ok := c.Get(userIDContextKey).(int32)
+	if !ok {
+		// Anonymous use should only fetch PUBLIC memos with specified user
+		if find.CreatorID == nil {
+			return nil, echo.NewHTTPError(http.StatusBadRequest, "Missing user to find memo")
+		}
+		find.VisibilityList = []store.Visibility{store.Public}
+	} else {
+		// Authorized user can fetch all PUBLIC/PROTECTED memo
+		visibilityList := []store.Visibility{store.Public, store.Protected}
+
+		// If Creator is authorized user (as default), PRIVATE memo is OK
+		if find.CreatorID == nil || *find.CreatorID == currentUserID {
+			find.CreatorID = &currentUserID
+			visibilityList = append(visibilityList, store.Private)
+		}
+		find.VisibilityList = visibilityList
+	}
+
+	rowStatus := store.RowStatus(c.QueryParam("rowStatus"))
+	if rowStatus != "" {
+		find.RowStatus = &rowStatus
+	}
+
+	contentSearch := []string{}
+	tag := c.QueryParam("tag")
+	if tag != "" {
+		contentSearch = append(contentSearch, "#"+tag)
+	}
+	content := c.QueryParam("content")
+	if content != "" {
+		contentSearch = append(contentSearch, content)
+	}
+	find.ContentSearch = contentSearch
+
+	if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
+		find.Limit = &limit
+	}
+	if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
+		find.Offset = &offset
+	}
+
+	memoDisplayWithUpdatedTs, err := s.getMemoDisplayWithUpdatedTsSettingValue(ctx)
+	if err != nil {
+		return nil, echo.NewHTTPError(http.StatusInternalServerError, "Failed to get memo display with updated ts setting value").SetInternal(err)
+	}
+	if memoDisplayWithUpdatedTs {
+		find.OrderByUpdatedTs = true
+	}
+
+	list, err := s.Store.ListMemos(ctx, find)
+	if err != nil {
+		return nil, echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err)
+	}
+
+	return list, nil
+}
+
 // DispatchMemoCreatedWebhook dispatches webhook when memo is created.
 func (s *APIV1Service) DispatchMemoCreatedWebhook(ctx context.Context, memo *Memo) error {
 	return s.dispatchMemoRelatedWebhook(ctx, memo, "memos.memo.created")

From 7297e624b00bc7b46eb78a1450fc383ff1ff6ffd Mon Sep 17 00:00:00 2001
From: Noah Alderton <noahlouisalderton@gmail.com>
Date: Sun, 14 Jan 2024 11:09:25 -0800
Subject: [PATCH 2/5] Add godoc for ExportMemos

---
 api/v1/memo.go | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/api/v1/memo.go b/api/v1/memo.go
index 0504ecac661b8..56144efdc3a9d 100644
--- a/api/v1/memo.go
+++ b/api/v1/memo.go
@@ -122,7 +122,7 @@ const maxContentLength = 1 << 30
 func (s *APIV1Service) registerMemoRoutes(g *echo.Group) {
 	g.GET("/memo", s.GetMemoList)
 	g.POST("/memo", s.CreateMemo)
-	g.GET("/memo/export", s.ExportMemo)
+	g.GET("/memo/export", s.ExportMemos)
 	g.GET("/memo/all", s.GetAllMemos)
 	g.GET("/memo/stats", s.GetMemoStats)
 	g.GET("/memo/:memoId", s.GetMemo)
@@ -164,7 +164,24 @@ func (s *APIV1Service) GetMemoList(c echo.Context) error {
 	return c.JSON(http.StatusOK, memoResponseList)
 }
 
-func (s *APIV1Service) ExportMemo(c echo.Context) error {
+// ExportMemos godoc
+//
+//	@Summary	Get a zip folder of Markdown files containing memos matching optional filters
+//	@Tags		memo
+//	@Produce	zip folder
+//	@Param		creatorId		query		int				false	"Creator ID"
+//	@Param		creatorUsername	query		string			false	"Creator username"
+//	@Param		rowStatus		query		store.RowStatus	false	"Row status"
+//	@Param		pinned			query		bool			false	"Pinned"
+//	@Param		tag				query		string			false	"Search for tag. Do not append #"
+//	@Param		content			query		string			false	"Search for content"
+//	@Param		limit			query		int				false	"Limit"
+//	@Param		offset			query		int				false	"Offset"
+//	@Success	200				{object}	[]byte	        "zip folder of Memos Markdown files"
+//	@Failure	400				{object}	nil				"Missing user to find memo"
+//	@Failure	500				{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | Failed to close zip file writer"
+//	@Router		/api/v1/memo [GET]
+func (s *APIV1Service) ExportMemos(c echo.Context) error {
 	list, httpError := s.getMemoList(c)
 	if httpError != nil {
 		return httpError

From 0974f3c677d99a2446627578a9c06788707ee279 Mon Sep 17 00:00:00 2001
From: Noah Alderton <noahlouisalderton@gmail.com>
Date: Sun, 14 Jan 2024 13:10:40 -0800
Subject: [PATCH 3/5] Add Export Memos button

---
 api/v1/docs.go                                | 44 ++++++++++---------
 api/v1/memo.go                                |  4 +-
 api/v1/swagger.yaml                           | 42 ++++++++++--------
 .../components/Settings/MyAccountSection.tsx  | 13 ++++++
 web/src/components/ShareMemoDialog.tsx        | 11 ++---
 web/src/helpers/api.ts                        |  4 ++
 web/src/helpers/utils.ts                      |  8 ++++
 web/src/locales/en.json                       |  1 +
 8 files changed, 78 insertions(+), 49 deletions(-)

diff --git a/api/v1/docs.go b/api/v1/docs.go
index b9da4740afec1..4303dc5504b18 100644
--- a/api/v1/docs.go
+++ b/api/v1/docs.go
@@ -1,5 +1,4 @@
-// Code generated by swaggo/swag. DO NOT EDIT.
-
+// Package v1 Code generated by swaggo/swag. DO NOT EDIT
 package v1
 
 import "github.com/swaggo/swag"
@@ -42,7 +41,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignIn"
+                            "$ref": "#/definitions/api_v1.SignIn"
                         }
                     }
                 ],
@@ -87,7 +86,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SSOSignIn"
+                            "$ref": "#/definitions/api_v1.SSOSignIn"
                         }
                     }
                 ],
@@ -154,7 +153,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignUp"
+                            "$ref": "#/definitions/api_v1.SignUp"
                         }
                     }
                 ],
@@ -380,12 +379,12 @@ const docTemplate = `{
         "/api/v1/memo": {
             "get": {
                 "produces": [
-                    "application/json"
+                    "application/zip"
                 ],
                 "tags": [
                     "memo"
                 ],
-                "summary": "Get a list of memos matching optional filters",
+                "summary": "Get a zip folder of Markdown files containing memos matching optional filters",
                 "parameters": [
                     {
                         "type": "integer",
@@ -442,11 +441,11 @@ const docTemplate = `{
                 ],
                 "responses": {
                     "200": {
-                        "description": "Memo list",
+                        "description": "zip folder of Memos Markdown files",
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/store.Memo"
+                                "type": "integer"
                             }
                         }
                     },
@@ -454,7 +453,7 @@ const docTemplate = `{
                         "description": "Missing user to find memo"
                     },
                     "500": {
-                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to compose memo response"
+                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | Failed to close zip file writer"
                     }
                 }
             },
@@ -477,7 +476,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateMemoRequest"
+                            "$ref": "#/definitions/api_v1.CreateMemoRequest"
                         }
                     }
                 ],
@@ -695,7 +694,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.PatchMemoRequest"
+                            "$ref": "#/definitions/api_v1.PatchMemoRequest"
                         }
                     }
                 ],
@@ -992,7 +991,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateResourceRequest"
+                            "$ref": "#/definitions/api_v1.CreateResourceRequest"
                         }
                     }
                 ],
@@ -1116,7 +1115,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpdateResourceRequest"
+                            "$ref": "#/definitions/api_v1.UpdateResourceRequest"
                         }
                     }
                 ],
@@ -1155,7 +1154,7 @@ const docTemplate = `{
                     "200": {
                         "description": "System GetSystemStatus",
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SystemStatus"
+                            "$ref": "#/definitions/api_v1.SystemStatus"
                         }
                     },
                     "401": {
@@ -1331,7 +1330,7 @@ const docTemplate = `{
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/api_v1.SystemSetting"
+                                "$ref": "#/definitions/github_com_usememos_memos_api_v1.SystemSetting"
                             }
                         }
                     },
@@ -1361,7 +1360,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.UpsertSystemSettingRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertSystemSettingRequest"
                         }
                     }
                 ],
@@ -1457,7 +1456,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertTagRequest"
+                            "$ref": "#/definitions/api_v1.UpsertTagRequest"
                         }
                     }
                 ],
@@ -1499,7 +1498,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.DeleteTagRequest"
+                            "$ref": "#/definitions/api_v1.DeleteTagRequest"
                         }
                     }
                 ],
@@ -1592,7 +1591,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateUserRequest"
+                            "$ref": "#/definitions/api_v1.CreateUserRequest"
                         }
                     }
                 ],
@@ -1773,7 +1772,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpdateUserRequest"
+                            "$ref": "#/definitions/api_v1.UpdateUserRequest"
                         }
                     }
                 ],
@@ -3253,6 +3252,9 @@ const docTemplate = `{
                 "id": {
                     "type": "integer"
                 },
+                "parentID": {
+                    "type": "integer"
+                },
                 "pinned": {
                     "description": "Composed fields",
                     "type": "boolean"
diff --git a/api/v1/memo.go b/api/v1/memo.go
index 56144efdc3a9d..1da4186963b87 100644
--- a/api/v1/memo.go
+++ b/api/v1/memo.go
@@ -168,7 +168,7 @@ func (s *APIV1Service) GetMemoList(c echo.Context) error {
 //
 //	@Summary	Get a zip folder of Markdown files containing memos matching optional filters
 //	@Tags		memo
-//	@Produce	zip folder
+//	@Produce	application/zip
 //	@Param		creatorId		query		int				false	"Creator ID"
 //	@Param		creatorUsername	query		string			false	"Creator username"
 //	@Param		rowStatus		query		store.RowStatus	false	"Row status"
@@ -177,7 +177,7 @@ func (s *APIV1Service) GetMemoList(c echo.Context) error {
 //	@Param		content			query		string			false	"Search for content"
 //	@Param		limit			query		int				false	"Limit"
 //	@Param		offset			query		int				false	"Offset"
-//	@Success	200				{object}	[]byte	        "zip folder of Memos Markdown files"
+//	@Success	200				{object}	[]byte			"zip folder of Memos Markdown files"
 //	@Failure	400				{object}	nil				"Missing user to find memo"
 //	@Failure	500				{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | Failed to close zip file writer"
 //	@Router		/api/v1/memo [GET]
diff --git a/api/v1/swagger.yaml b/api/v1/swagger.yaml
index e3576e23cd54a..0c97c1dd97feb 100644
--- a/api/v1/swagger.yaml
+++ b/api/v1/swagger.yaml
@@ -932,6 +932,8 @@ definitions:
         type: integer
       id:
         type: integer
+      parentID:
+        type: integer
       pinned:
         description: Composed fields
         type: boolean
@@ -1088,7 +1090,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignIn'
+          $ref: '#/definitions/api_v1.SignIn'
       produces:
       - application/json
       responses:
@@ -1120,7 +1122,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SSOSignIn'
+          $ref: '#/definitions/api_v1.SSOSignIn'
       produces:
       - application/json
       responses:
@@ -1168,7 +1170,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignUp'
+          $ref: '#/definitions/api_v1.SignUp'
       produces:
       - application/json
       responses:
@@ -1359,20 +1361,22 @@ paths:
         name: offset
         type: integer
       produces:
-      - application/json
+      - application/zip
       responses:
         "200":
-          description: Memo list
+          description: zip folder of Memos Markdown files
           schema:
             items:
-              $ref: '#/definitions/store.Memo'
+              type: integer
             type: array
         "400":
           description: Missing user to find memo
         "500":
           description: Failed to get memo display with updated ts setting value |
-            Failed to fetch memo list | Failed to compose memo response
-      summary: Get a list of memos matching optional filters
+            Failed to fetch memo list | Failed to create memo file | Failed to close
+            zip file writer
+      summary: Get a zip folder of Markdown files containing memos matching optional
+        filters
       tags:
       - memo
     post:
@@ -1387,7 +1391,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateMemoRequest'
+          $ref: '#/definitions/api_v1.CreateMemoRequest'
       produces:
       - application/json
       responses:
@@ -1484,7 +1488,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.PatchMemoRequest'
+          $ref: '#/definitions/api_v1.PatchMemoRequest'
       produces:
       - application/json
       responses:
@@ -1743,7 +1747,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateResourceRequest'
+          $ref: '#/definitions/api_v1.CreateResourceRequest'
       produces:
       - application/json
       responses:
@@ -1801,7 +1805,7 @@ paths:
         name: patch
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpdateResourceRequest'
+          $ref: '#/definitions/api_v1.UpdateResourceRequest'
       produces:
       - application/json
       responses:
@@ -1856,7 +1860,7 @@ paths:
         "200":
           description: System GetSystemStatus
           schema:
-            $ref: '#/definitions/github_com_usememos_memos_api_v1.SystemStatus'
+            $ref: '#/definitions/api_v1.SystemStatus'
         "401":
           description: Missing user in session | Unauthorized
         "500":
@@ -1975,7 +1979,7 @@ paths:
           description: System setting list
           schema:
             items:
-              $ref: '#/definitions/api_v1.SystemSetting'
+              $ref: '#/definitions/github_com_usememos_memos_api_v1.SystemSetting'
             type: array
         "401":
           description: Missing user in session | Unauthorized
@@ -1993,7 +1997,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.UpsertSystemSettingRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertSystemSettingRequest'
       produces:
       - application/json
       responses:
@@ -2055,7 +2059,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertTagRequest'
+          $ref: '#/definitions/api_v1.UpsertTagRequest'
       produces:
       - application/json
       responses:
@@ -2082,7 +2086,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.DeleteTagRequest'
+          $ref: '#/definitions/api_v1.DeleteTagRequest'
       produces:
       - application/json
       responses:
@@ -2142,7 +2146,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateUserRequest'
+          $ref: '#/definitions/api_v1.CreateUserRequest'
       produces:
       - application/json
       responses:
@@ -2224,7 +2228,7 @@ paths:
         name: patch
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpdateUserRequest'
+          $ref: '#/definitions/api_v1.UpdateUserRequest'
       produces:
       - application/json
       responses:
diff --git a/web/src/components/Settings/MyAccountSection.tsx b/web/src/components/Settings/MyAccountSection.tsx
index 53c4ae34e94e2..b57c1089c0ca6 100644
--- a/web/src/components/Settings/MyAccountSection.tsx
+++ b/web/src/components/Settings/MyAccountSection.tsx
@@ -5,6 +5,8 @@ import showChangePasswordDialog from "../ChangePasswordDialog";
 import showUpdateAccountDialog from "../UpdateAccountDialog";
 import UserAvatar from "../UserAvatar";
 import AccessTokenSection from "./AccessTokenSection";
+import * as api from "@/helpers/api";
+import { downloadFileFromUrl } from "@/helpers/utils";
 
 const MyAccountSection = () => {
   const t = useTranslate();
@@ -27,6 +29,9 @@ const MyAccountSection = () => {
         <Button variant="outlined" onClick={showChangePasswordDialog}>
           {t("setting.account-section.change-password")}
         </Button>
+        <Button variant="outlined" onClick={downloadExportedMemos}>
+          {t("setting.account-section.export-memos")}
+        </Button>
       </div>
 
       <AccessTokenSection />
@@ -34,4 +39,12 @@ const MyAccountSection = () => {
   );
 };
 
+const downloadExportedMemos = () => {
+  api.exportMemos().then(response => {
+      const url = window.URL.createObjectURL(new Blob([response.data]));
+      downloadFileFromUrl(url, "memos-export.zip");
+      URL.revokeObjectURL(url);
+  })
+}
+
 export default MyAccountSection;
diff --git a/web/src/components/ShareMemoDialog.tsx b/web/src/components/ShareMemoDialog.tsx
index 305a02691017d..ff82828091e18 100644
--- a/web/src/components/ShareMemoDialog.tsx
+++ b/web/src/components/ShareMemoDialog.tsx
@@ -14,6 +14,7 @@ import MemoContent from "./MemoContent";
 import MemoResourceListView from "./MemoResourceListView";
 import UserAvatar from "./UserAvatar";
 import "@/less/share-memo-dialog.less";
+import { downloadFileFromUrl } from "@/helpers/utils";
 
 interface Props extends DialogProps {
   memo: Memo;
@@ -51,6 +52,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
       .then((url) => {
         downloadFileFromUrl(url, `memos-${getDateTimeString(Date.now())}.png`);
         downloadingImageState.setFinish();
+        URL.revokeObjectURL(url);
       })
       .catch((err) => {
         console.error(err);
@@ -59,14 +61,9 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
 
   const handleDownloadTextFileBtnClick = () => {
     const blob = new Blob([memo.content], { type: "text/plain;charset=utf-8" });
+    const url = URL.createObjectURL(blob);
     downloadFileFromUrl(URL.createObjectURL(blob), `memos-${getDateTimeString(Date.now())}.md`);
-  };
-
-  const downloadFileFromUrl = (url: string, filename: string) => {
-    const a = document.createElement("a");
-    a.href = url;
-    a.download = filename;
-    a.click();
+    URL.revokeObjectURL(url);
   };
 
   const handleCopyLinkBtnClick = () => {
diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts
index 18384b819648f..a24dc03537f59 100644
--- a/web/src/helpers/api.ts
+++ b/web/src/helpers/api.ts
@@ -13,6 +13,10 @@ export function upsertSystemSetting(systemSetting: SystemSetting) {
   return axios.post<SystemSetting>("/api/v1/system/setting", systemSetting);
 }
 
+export function exportMemos() {
+  return axios.get("/api/v1/memo/export", {responseType: 'blob'});
+}
+
 export function vacuumDatabase() {
   return axios.post("/api/v1/system/vacuum");
 }
diff --git a/web/src/helpers/utils.ts b/web/src/helpers/utils.ts
index b03b20c83d2da..7613dc64fb07e 100644
--- a/web/src/helpers/utils.ts
+++ b/web/src/helpers/utils.ts
@@ -83,3 +83,11 @@ export const formatBytes = (bytes: number) => {
     i = Math.floor(Math.log(bytes) / Math.log(k));
   return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
 };
+
+export const downloadFileFromUrl = (url: string, filename: string) => {
+  const a = document.createElement("a");
+  a.href = url;
+  a.download = filename;
+  a.click();
+  a.remove();
+};
diff --git a/web/src/locales/en.json b/web/src/locales/en.json
index f9577ef59511a..8ffa80b1fdf58 100644
--- a/web/src/locales/en.json
+++ b/web/src/locales/en.json
@@ -176,6 +176,7 @@
       "email-note": "Optional",
       "update-information": "Update Information",
       "change-password": "Change password",
+      "export-memos": "Export Memos",
       "reset-api": "Reset API",
       "openapi-title": "OpenAPI",
       "openapi-reset": "Reset OpenAPI Key",

From 3cdc73d767c139fec106066a12eca6e61193745c Mon Sep 17 00:00:00 2001
From: Noah Alderton <noahlouisalderton@gmail.com>
Date: Sun, 14 Jan 2024 13:42:35 -0800
Subject: [PATCH 4/5] Resolve style issues

---
 api/v1/docs.go                                | 24 ++++++++---------
 api/v1/memo.go                                | 11 +++++---
 api/v1/swagger.yaml                           | 26 +++++++++----------
 .../components/Settings/MyAccountSection.tsx  | 16 ++++++------
 web/src/components/ShareMemoDialog.tsx        |  2 +-
 web/src/helpers/api.ts                        |  2 +-
 6 files changed, 42 insertions(+), 39 deletions(-)

diff --git a/api/v1/docs.go b/api/v1/docs.go
index 4303dc5504b18..523a621ecb918 100644
--- a/api/v1/docs.go
+++ b/api/v1/docs.go
@@ -41,7 +41,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.SignIn"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignIn"
                         }
                     }
                 ],
@@ -86,7 +86,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.SSOSignIn"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SSOSignIn"
                         }
                     }
                 ],
@@ -153,7 +153,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.SignUp"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignUp"
                         }
                     }
                 ],
@@ -453,7 +453,7 @@ const docTemplate = `{
                         "description": "Missing user to find memo"
                     },
                     "500": {
-                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | Failed to close zip file writer"
+                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | \"Failed to write to memo file | Failed to close zip file writer"
                     }
                 }
             },
@@ -476,7 +476,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.CreateMemoRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateMemoRequest"
                         }
                     }
                 ],
@@ -694,7 +694,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.PatchMemoRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.PatchMemoRequest"
                         }
                     }
                 ],
@@ -746,7 +746,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest"
+                            "$ref": "#/definitions/api_v1.UpsertMemoOrganizerRequest"
                         }
                     }
                 ],
@@ -837,7 +837,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.UpsertMemoRelationRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest"
                         }
                     }
                 ],
@@ -991,7 +991,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.CreateResourceRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateResourceRequest"
                         }
                     }
                 ],
@@ -1115,7 +1115,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.UpdateResourceRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpdateResourceRequest"
                         }
                     }
                 ],
@@ -1591,7 +1591,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.CreateUserRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateUserRequest"
                         }
                     }
                 ],
@@ -1772,7 +1772,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.UpdateUserRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpdateUserRequest"
                         }
                     }
                 ],
diff --git a/api/v1/memo.go b/api/v1/memo.go
index 1da4186963b87..c7ca7193948f7 100644
--- a/api/v1/memo.go
+++ b/api/v1/memo.go
@@ -148,7 +148,7 @@ func (s *APIV1Service) registerMemoRoutes(g *echo.Group) {
 //	@Failure	500				{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to compose memo response"
 //	@Router		/api/v1/memo [GET]
 func (s *APIV1Service) GetMemoList(c echo.Context) error {
-	list, httpError := s.getMemoList(c)
+	list, httpError := s.getMemosAsList(c)
 	if httpError != nil {
 		return httpError
 	}
@@ -179,10 +179,10 @@ func (s *APIV1Service) GetMemoList(c echo.Context) error {
 //	@Param		offset			query		int				false	"Offset"
 //	@Success	200				{object}	[]byte			"zip folder of Memos Markdown files"
 //	@Failure	400				{object}	nil				"Missing user to find memo"
-//	@Failure	500				{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | Failed to close zip file writer"
+//	@Failure	500				{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | "Failed to write to memo file | Failed to close zip file writer"
 //	@Router		/api/v1/memo [GET]
 func (s *APIV1Service) ExportMemos(c echo.Context) error {
-	list, httpError := s.getMemoList(c)
+	list, httpError := s.getMemosAsList(c)
 	if httpError != nil {
 		return httpError
 	}
@@ -195,6 +195,9 @@ func (s *APIV1Service) ExportMemos(c echo.Context) error {
 			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create memo file").SetInternal(err)
 		}
 		_, err = f.Write([]byte(memo.Content))
+		if err != nil {
+			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to write to memo file").SetInternal(err)
+		}
 	}
 	err := writer.Close()
 	if err != nil {
@@ -926,7 +929,7 @@ func getIDListDiff(oldList, newList []int32) (addedList, removedList []int32) {
 	return addedList, removedList
 }
 
-func (s *APIV1Service) getMemoList(c echo.Context) ([]*store.Memo, *echo.HTTPError) {
+func (s *APIV1Service) getMemosAsList(c echo.Context) ([]*store.Memo, *echo.HTTPError) {
 	ctx := c.Request().Context()
 	find := &store.FindMemo{
 		OrderByPinned: true,
diff --git a/api/v1/swagger.yaml b/api/v1/swagger.yaml
index 0c97c1dd97feb..5feea7a152684 100644
--- a/api/v1/swagger.yaml
+++ b/api/v1/swagger.yaml
@@ -1090,7 +1090,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.SignIn'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignIn'
       produces:
       - application/json
       responses:
@@ -1122,7 +1122,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.SSOSignIn'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.SSOSignIn'
       produces:
       - application/json
       responses:
@@ -1170,7 +1170,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.SignUp'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignUp'
       produces:
       - application/json
       responses:
@@ -1373,8 +1373,8 @@ paths:
           description: Missing user to find memo
         "500":
           description: Failed to get memo display with updated ts setting value |
-            Failed to fetch memo list | Failed to create memo file | Failed to close
-            zip file writer
+            Failed to fetch memo list | Failed to create memo file | "Failed to write
+            to memo file | Failed to close zip file writer
       summary: Get a zip folder of Markdown files containing memos matching optional
         filters
       tags:
@@ -1391,7 +1391,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.CreateMemoRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateMemoRequest'
       produces:
       - application/json
       responses:
@@ -1488,7 +1488,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.PatchMemoRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.PatchMemoRequest'
       produces:
       - application/json
       responses:
@@ -1525,7 +1525,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest'
+          $ref: '#/definitions/api_v1.UpsertMemoOrganizerRequest'
       produces:
       - application/json
       responses:
@@ -1587,7 +1587,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.UpsertMemoRelationRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest'
       produces:
       - application/json
       responses:
@@ -1747,7 +1747,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.CreateResourceRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateResourceRequest'
       produces:
       - application/json
       responses:
@@ -1805,7 +1805,7 @@ paths:
         name: patch
         required: true
         schema:
-          $ref: '#/definitions/api_v1.UpdateResourceRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpdateResourceRequest'
       produces:
       - application/json
       responses:
@@ -2146,7 +2146,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.CreateUserRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateUserRequest'
       produces:
       - application/json
       responses:
@@ -2228,7 +2228,7 @@ paths:
         name: patch
         required: true
         schema:
-          $ref: '#/definitions/api_v1.UpdateUserRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpdateUserRequest'
       produces:
       - application/json
       responses:
diff --git a/web/src/components/Settings/MyAccountSection.tsx b/web/src/components/Settings/MyAccountSection.tsx
index b57c1089c0ca6..bd8a95da8199b 100644
--- a/web/src/components/Settings/MyAccountSection.tsx
+++ b/web/src/components/Settings/MyAccountSection.tsx
@@ -1,12 +1,12 @@
 import { Button } from "@mui/joy";
+import * as api from "@/helpers/api";
+import { downloadFileFromUrl } from "@/helpers/utils";
 import useCurrentUser from "@/hooks/useCurrentUser";
 import { useTranslate } from "@/utils/i18n";
 import showChangePasswordDialog from "../ChangePasswordDialog";
 import showUpdateAccountDialog from "../UpdateAccountDialog";
 import UserAvatar from "../UserAvatar";
 import AccessTokenSection from "./AccessTokenSection";
-import * as api from "@/helpers/api";
-import { downloadFileFromUrl } from "@/helpers/utils";
 
 const MyAccountSection = () => {
   const t = useTranslate();
@@ -40,11 +40,11 @@ const MyAccountSection = () => {
 };
 
 const downloadExportedMemos = () => {
-  api.exportMemos().then(response => {
-      const url = window.URL.createObjectURL(new Blob([response.data]));
-      downloadFileFromUrl(url, "memos-export.zip");
-      URL.revokeObjectURL(url);
-  })
-}
+  api.exportMemos().then((response) => {
+    const url = window.URL.createObjectURL(new Blob([response.data]));
+    downloadFileFromUrl(url, "memos-export.zip");
+    URL.revokeObjectURL(url);
+  });
+};
 
 export default MyAccountSection;
diff --git a/web/src/components/ShareMemoDialog.tsx b/web/src/components/ShareMemoDialog.tsx
index ff82828091e18..1d6528f1f741c 100644
--- a/web/src/components/ShareMemoDialog.tsx
+++ b/web/src/components/ShareMemoDialog.tsx
@@ -3,6 +3,7 @@ import copy from "copy-to-clipboard";
 import React, { useEffect, useRef } from "react";
 import { toast } from "react-hot-toast";
 import { getDateTimeString } from "@/helpers/datetime";
+import { downloadFileFromUrl } from "@/helpers/utils";
 import useLoading from "@/hooks/useLoading";
 import toImage from "@/labs/html2image";
 import { useUserStore, extractUsernameFromName } from "@/store/v1";
@@ -14,7 +15,6 @@ import MemoContent from "./MemoContent";
 import MemoResourceListView from "./MemoResourceListView";
 import UserAvatar from "./UserAvatar";
 import "@/less/share-memo-dialog.less";
-import { downloadFileFromUrl } from "@/helpers/utils";
 
 interface Props extends DialogProps {
   memo: Memo;
diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts
index a24dc03537f59..b721f2880231b 100644
--- a/web/src/helpers/api.ts
+++ b/web/src/helpers/api.ts
@@ -14,7 +14,7 @@ export function upsertSystemSetting(systemSetting: SystemSetting) {
 }
 
 export function exportMemos() {
-  return axios.get("/api/v1/memo/export", {responseType: 'blob'});
+  return axios.get("/api/v1/memo/export", { responseType: "blob" });
 }
 
 export function vacuumDatabase() {

From 5a44e38a6cec5d6380cc082ecf6569cb651605d8 Mon Sep 17 00:00:00 2001
From: Noah Alderton <noahlouisalderton@gmail.com>
Date: Sun, 14 Jan 2024 13:44:03 -0800
Subject: [PATCH 5/5] Fix incorrect docs

---
 api/v1/docs.go      | 118 +++++++++++++++++++++++++++++++++++++-------
 api/v1/memo.go      |   2 +-
 api/v1/swagger.yaml |  95 +++++++++++++++++++++++++++--------
 3 files changed, 176 insertions(+), 39 deletions(-)

diff --git a/api/v1/docs.go b/api/v1/docs.go
index 523a621ecb918..ec497356943f6 100644
--- a/api/v1/docs.go
+++ b/api/v1/docs.go
@@ -41,7 +41,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignIn"
+                            "$ref": "#/definitions/api_v1.SignIn"
                         }
                     }
                 ],
@@ -86,7 +86,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SSOSignIn"
+                            "$ref": "#/definitions/api_v1.SSOSignIn"
                         }
                     }
                 ],
@@ -153,7 +153,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignUp"
+                            "$ref": "#/definitions/api_v1.SignUp"
                         }
                     }
                 ],
@@ -198,7 +198,7 @@ const docTemplate = `{
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProvider"
+                                "$ref": "#/definitions/api_v1.IdentityProvider"
                             }
                         }
                     },
@@ -225,7 +225,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateIdentityProviderRequest"
+                            "$ref": "#/definitions/api_v1.CreateIdentityProviderRequest"
                         }
                     }
                 ],
@@ -353,7 +353,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpdateIdentityProviderRequest"
+                            "$ref": "#/definitions/api_v1.UpdateIdentityProviderRequest"
                         }
                     }
                 ],
@@ -379,12 +379,12 @@ const docTemplate = `{
         "/api/v1/memo": {
             "get": {
                 "produces": [
-                    "application/zip"
+                    "application/json"
                 ],
                 "tags": [
                     "memo"
                 ],
-                "summary": "Get a zip folder of Markdown files containing memos matching optional filters",
+                "summary": "Get a list of memos matching optional filters",
                 "parameters": [
                     {
                         "type": "integer",
@@ -441,11 +441,11 @@ const docTemplate = `{
                 ],
                 "responses": {
                     "200": {
-                        "description": "zip folder of Memos Markdown files",
+                        "description": "Memo list",
                         "schema": {
                             "type": "array",
                             "items": {
-                                "type": "integer"
+                                "$ref": "#/definitions/store.Memo"
                             }
                         }
                     },
@@ -453,7 +453,7 @@ const docTemplate = `{
                         "description": "Missing user to find memo"
                     },
                     "500": {
-                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | \"Failed to write to memo file | Failed to close zip file writer"
+                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to compose memo response"
                     }
                 }
             },
@@ -542,6 +542,88 @@ const docTemplate = `{
                 }
             }
         },
+        "/api/v1/memo/export": {
+            "get": {
+                "produces": [
+                    "application/zip"
+                ],
+                "tags": [
+                    "memo"
+                ],
+                "summary": "Get a zip folder of Markdown files containing memos matching optional filters",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "Creator ID",
+                        "name": "creatorId",
+                        "in": "query"
+                    },
+                    {
+                        "type": "string",
+                        "description": "Creator username",
+                        "name": "creatorUsername",
+                        "in": "query"
+                    },
+                    {
+                        "enum": [
+                            "NORMAL",
+                            "ARCHIVED"
+                        ],
+                        "type": "string",
+                        "description": "Row status",
+                        "name": "rowStatus",
+                        "in": "query"
+                    },
+                    {
+                        "type": "boolean",
+                        "description": "Pinned",
+                        "name": "pinned",
+                        "in": "query"
+                    },
+                    {
+                        "type": "string",
+                        "description": "Search for tag. Do not append #",
+                        "name": "tag",
+                        "in": "query"
+                    },
+                    {
+                        "type": "string",
+                        "description": "Search for content",
+                        "name": "content",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Limit",
+                        "name": "limit",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "Offset",
+                        "name": "offset",
+                        "in": "query"
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "zip folder of Memos Markdown files",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "type": "integer"
+                            }
+                        }
+                    },
+                    "400": {
+                        "description": "Missing user to find memo"
+                    },
+                    "500": {
+                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | \"Failed to write to memo file | Failed to close zip file writer"
+                    }
+                }
+            }
+        },
         "/api/v1/memo/stats": {
             "get": {
                 "description": "Used to generate the heatmap",
@@ -746,7 +828,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/api_v1.UpsertMemoOrganizerRequest"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest"
                         }
                     }
                 ],
@@ -837,7 +919,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest"
+                            "$ref": "#/definitions/api_v1.UpsertMemoRelationRequest"
                         }
                     }
                 ],
@@ -991,7 +1073,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateResourceRequest"
+                            "$ref": "#/definitions/api_v1.CreateResourceRequest"
                         }
                     }
                 ],
@@ -1115,7 +1197,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpdateResourceRequest"
+                            "$ref": "#/definitions/api_v1.UpdateResourceRequest"
                         }
                     }
                 ],
@@ -1154,7 +1236,7 @@ const docTemplate = `{
                     "200": {
                         "description": "System GetSystemStatus",
                         "schema": {
-                            "$ref": "#/definitions/api_v1.SystemStatus"
+                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SystemStatus"
                         }
                     },
                     "401": {
@@ -1330,7 +1412,7 @@ const docTemplate = `{
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/github_com_usememos_memos_api_v1.SystemSetting"
+                                "$ref": "#/definitions/api_v1.SystemSetting"
                             }
                         }
                     },
@@ -1360,7 +1442,7 @@ const docTemplate = `{
                         "in": "body",
                         "required": true,
                         "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertSystemSettingRequest"
+                            "$ref": "#/definitions/api_v1.UpsertSystemSettingRequest"
                         }
                     }
                 ],
diff --git a/api/v1/memo.go b/api/v1/memo.go
index c7ca7193948f7..01b6702d6220b 100644
--- a/api/v1/memo.go
+++ b/api/v1/memo.go
@@ -180,7 +180,7 @@ func (s *APIV1Service) GetMemoList(c echo.Context) error {
 //	@Success	200				{object}	[]byte			"zip folder of Memos Markdown files"
 //	@Failure	400				{object}	nil				"Missing user to find memo"
 //	@Failure	500				{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch memo list | Failed to create memo file | "Failed to write to memo file | Failed to close zip file writer"
-//	@Router		/api/v1/memo [GET]
+//	@Router		/api/v1/memo/export [GET]
 func (s *APIV1Service) ExportMemos(c echo.Context) error {
 	list, httpError := s.getMemosAsList(c)
 	if httpError != nil {
diff --git a/api/v1/swagger.yaml b/api/v1/swagger.yaml
index 5feea7a152684..cd2e04606305d 100644
--- a/api/v1/swagger.yaml
+++ b/api/v1/swagger.yaml
@@ -1090,7 +1090,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignIn'
+          $ref: '#/definitions/api_v1.SignIn'
       produces:
       - application/json
       responses:
@@ -1122,7 +1122,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SSOSignIn'
+          $ref: '#/definitions/api_v1.SSOSignIn'
       produces:
       - application/json
       responses:
@@ -1170,7 +1170,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignUp'
+          $ref: '#/definitions/api_v1.SignUp'
       produces:
       - application/json
       responses:
@@ -1203,7 +1203,7 @@ paths:
           description: List of available identity providers
           schema:
             items:
-              $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProvider'
+              $ref: '#/definitions/api_v1.IdentityProvider'
             type: array
         "500":
           description: Failed to find identity provider list | Failed to find user
@@ -1219,7 +1219,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateIdentityProviderRequest'
+          $ref: '#/definitions/api_v1.CreateIdentityProviderRequest'
       produces:
       - application/json
       responses:
@@ -1304,7 +1304,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpdateIdentityProviderRequest'
+          $ref: '#/definitions/api_v1.UpdateIdentityProviderRequest'
       produces:
       - application/json
       responses:
@@ -1361,22 +1361,20 @@ paths:
         name: offset
         type: integer
       produces:
-      - application/zip
+      - application/json
       responses:
         "200":
-          description: zip folder of Memos Markdown files
+          description: Memo list
           schema:
             items:
-              type: integer
+              $ref: '#/definitions/store.Memo'
             type: array
         "400":
           description: Missing user to find memo
         "500":
           description: Failed to get memo display with updated ts setting value |
-            Failed to fetch memo list | Failed to create memo file | "Failed to write
-            to memo file | Failed to close zip file writer
-      summary: Get a zip folder of Markdown files containing memos matching optional
-        filters
+            Failed to fetch memo list | Failed to compose memo response
+      summary: Get a list of memos matching optional filters
       tags:
       - memo
     post:
@@ -1525,7 +1523,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/api_v1.UpsertMemoOrganizerRequest'
+          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest'
       produces:
       - application/json
       responses:
@@ -1587,7 +1585,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest'
+          $ref: '#/definitions/api_v1.UpsertMemoRelationRequest'
       produces:
       - application/json
       responses:
@@ -1670,6 +1668,63 @@ paths:
       summary: Get a list of public memos matching optional filters
       tags:
       - memo
+  /api/v1/memo/export:
+    get:
+      parameters:
+      - description: Creator ID
+        in: query
+        name: creatorId
+        type: integer
+      - description: Creator username
+        in: query
+        name: creatorUsername
+        type: string
+      - description: Row status
+        enum:
+        - NORMAL
+        - ARCHIVED
+        in: query
+        name: rowStatus
+        type: string
+      - description: Pinned
+        in: query
+        name: pinned
+        type: boolean
+      - description: 'Search for tag. Do not append #'
+        in: query
+        name: tag
+        type: string
+      - description: Search for content
+        in: query
+        name: content
+        type: string
+      - description: Limit
+        in: query
+        name: limit
+        type: integer
+      - description: Offset
+        in: query
+        name: offset
+        type: integer
+      produces:
+      - application/zip
+      responses:
+        "200":
+          description: zip folder of Memos Markdown files
+          schema:
+            items:
+              type: integer
+            type: array
+        "400":
+          description: Missing user to find memo
+        "500":
+          description: Failed to get memo display with updated ts setting value |
+            Failed to fetch memo list | Failed to create memo file | "Failed to write
+            to memo file | Failed to close zip file writer
+      summary: Get a zip folder of Markdown files containing memos matching optional
+        filters
+      tags:
+      - memo
   /api/v1/memo/stats:
     get:
       description: Used to generate the heatmap
@@ -1747,7 +1802,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateResourceRequest'
+          $ref: '#/definitions/api_v1.CreateResourceRequest'
       produces:
       - application/json
       responses:
@@ -1805,7 +1860,7 @@ paths:
         name: patch
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpdateResourceRequest'
+          $ref: '#/definitions/api_v1.UpdateResourceRequest'
       produces:
       - application/json
       responses:
@@ -1860,7 +1915,7 @@ paths:
         "200":
           description: System GetSystemStatus
           schema:
-            $ref: '#/definitions/api_v1.SystemStatus'
+            $ref: '#/definitions/github_com_usememos_memos_api_v1.SystemStatus'
         "401":
           description: Missing user in session | Unauthorized
         "500":
@@ -1979,7 +2034,7 @@ paths:
           description: System setting list
           schema:
             items:
-              $ref: '#/definitions/github_com_usememos_memos_api_v1.SystemSetting'
+              $ref: '#/definitions/api_v1.SystemSetting'
             type: array
         "401":
           description: Missing user in session | Unauthorized
@@ -1997,7 +2052,7 @@ paths:
         name: body
         required: true
         schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertSystemSettingRequest'
+          $ref: '#/definitions/api_v1.UpsertSystemSettingRequest'
       produces:
       - application/json
       responses: