CVE-2024-29030 (published 2024-04-19T15:15Z)

memos is a privacy-first, lightweight note-taking service. In memos 0.13.2, an SSRF vulnerability exists at the /api/resource that allows authenticated users to enumerate the internal network.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/usememos/memos
commit_details: repo_url: https://github.com/usememos/memos
author: Steven
author_date: 2024-04-13 11:01:16+08:00
msg: chore: retire legacy api
repository: repo_url: https://github.com/usememos/memos
repo_name: usememos/memos
description: An open source, lightweight note-taking service. Easily capture and share your great thoughts.
repo_language: Go
owner: usememos
file_changes: filename: presign_link.go
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,6 @@ package jobs
 
 import (
 	"context"
-	"encoding/json"
 	"log/slog"
 	"strings"
 	"time"
@@ -10,7 +9,9 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/usememos/memos/plugin/storage/s3"
-	apiv1 "github.com/usememos/memos/server/route/api/v1"
+	apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
+	storepb "github.com/usememos/memos/proto/gen/store"
+	apiv2 "github.com/usememos/memos/server/route/api/v2"
 	"github.com/usememos/memos/store"
 )
 
@@ -95,44 +96,35 @@ func signExternalLinks(ctx context.Context, dataStore *store.Store) error {
 // Returns error only in case of internal problems (ie: database or configuration issues).
 // May return nil client and nil error.
 func findObjectStorage(ctx context.Context, dataStore *store.Store) (*s3.Client, error) {
-	systemSettingStorageServiceID, err := dataStore.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{Name: apiv1.SystemSettingStorageServiceIDName.String()})
+	workspaceStorageSetting, err := dataStore.GetWorkspaceStorageSetting(ctx)
 	if err != nil {
-		return nil, errors.Wrap(err, "Failed to find SystemSettingStorageServiceIDName")
+		return nil, errors.Wrap(err, "Failed to find workspaceStorageSetting")
 	}
-
-	storageServiceID := apiv1.DefaultStorage
-	if systemSettingStorageServiceID != nil {
-		err = json.Unmarshal([]byte(systemSettingStorageServiceID.Value), &storageServiceID)
-		if err != nil {
-			return nil, errors.Wrap(err, "Failed to unmarshal storage service id")
-		}
+	if workspaceStorageSetting.StorageType != storepb.WorkspaceStorageSetting_STORAGE_TYPE_EXTERNAL || workspaceStorageSetting.ActivedExternalStorageId == nil {
+		return nil, nil
 	}
-	storage, err := dataStore.GetStorage(ctx, &store.FindStorage{ID: &storageServiceID})
+	storage, err := dataStore.GetStorageV1(ctx, &store.FindStorage{ID: workspaceStorageSetting.ActivedExternalStorageId})
 	if err != nil {
-		return nil, errors.Wrap(err, "Failed to find StorageServiceID")
+		return nil, errors.Wrap(err, "Failed to find storage")
 	}
-
 	if storage == nil {
-		return nil, nil // storage not configured - not an error, just return empty ref
+		return nil, nil
 	}
-	storageMessage, err := apiv1.ConvertStorageFromStore(storage)
 
-	if err != nil {
-		return nil, errors.Wrap(err, "Failed to ConvertStorageFromStore")
-	}
-	if storageMessage.Type != apiv1.StorageS3 {
+	storageMessage := apiv2.ConvertStorageFromStore(storage)
+	if storageMessage.Type != apiv2pb.Storage_S3 {
 		return nil, nil
 	}
 
-	s3Config := storageMessage.Config.S3Config
+	s3Config := storageMessage.Config.GetS3Config()
 	return s3.NewClient(ctx, &s3.Config{
 		AccessKey: s3Config.AccessKey,
 		SecretKey: s3Config.SecretKey,
 		EndPoint:  s3Config.EndPoint,
 		Region:    s3Config.Region,
 		Bucket:    s3Config.Bucket,
-		URLPrefix: s3Config.URLPrefix,
-		URLSuffix: s3Config.URLSuffix,
+		URLPrefix: s3Config.UrlPrefix,
+		URLSuffix: s3Config.UrlSuffix,
 		PreSign:   s3Config.PreSign,
 	})
 }

programming_language: Go
filename: telegram.go
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,6 @@
 package integration
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"path/filepath"
@@ -19,7 +18,6 @@ import (
 	"github.com/usememos/memos/plugin/telegram"
 	"github.com/usememos/memos/plugin/webhook"
 	storepb "github.com/usememos/memos/proto/gen/store"
-	apiv1 "github.com/usememos/memos/server/route/api/v1"
 	apiv2 "github.com/usememos/memos/server/route/api/v2"
 	"github.com/usememos/memos/store"
 )
@@ -126,9 +124,10 @@ func (t *TelegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
 			Type:      attachment.GetMimeType(),
 			Size:      attachment.FileSize,
 			MemoID:    &memoMessage.ID,
+			Blob:      attachment.Data,
 		}
 
-		err := apiv1.SaveResourceBlob(ctx, t.store, &create, bytes.NewReader(attachment.Data))
+		err := apiv2.SaveResourceBlob(ctx, t.store, &create)
 		if err != nil {
 			_, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("Failed to SaveResourceBlob: %s", err), nil)
 			return err

programming_language: Go
filename: auth.go
change_type: ModificationType.DELETE
diff: @@ -1,253 +0,0 @@
-package v1
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"strings"
-	"time"
-
-	"github.com/labstack/echo/v4"
-	"github.com/pkg/errors"
-	"golang.org/x/crypto/bcrypt"
-
-	"github.com/usememos/memos/internal/util"
-	storepb "github.com/usememos/memos/proto/gen/store"
-	"github.com/usememos/memos/server/route/api/auth"
-	"github.com/usememos/memos/store"
-)
-
-type SignIn struct {
-	Username string `json:"username"`
-	Password string `json:"password"`
-	Remember bool   `json:"remember"`
-}
-
-type SSOSignIn struct {
-	IdentityProviderID int32  `json:"identityProviderId"`
-	Code               string `json:"code"`
-	RedirectURI        string `json:"redirectUri"`
-}
-
-type SignUp struct {
-	Username string `json:"username"`
-	Password string `json:"password"`
-}
-
-func (s *APIV1Service) registerAuthRoutes(g *echo.Group) {
-	g.POST("/auth/signin", s.SignIn)
-	g.POST("/auth/signout", s.SignOut)
-	g.POST("/auth/signup", s.SignUp)
-}
-
-// SignIn godoc
-//
-//	@Summary	Sign-in to memos.
-//	@Tags		auth
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		SignIn		true	"Sign-in object"
-//	@Success	200		{object}	store.User	"User information"
-//	@Failure	400		{object}	nil			"Malformatted signin request"
-//	@Failure	401		{object}	nil			"Password login is deactivated | Incorrect login credentials, please try again"
-//	@Failure	403		{object}	nil			"User has been archived with username %s"
-//	@Failure	500		{object}	nil			"Failed to find system setting | Failed to unmarshal system setting | Incorrect login credentials, please try again | Failed to generate tokens | Failed to create activity"
-//	@Router		/api/v1/auth/signin [POST]
-func (s *APIV1Service) SignIn(c echo.Context) error {
-	ctx := c.Request().Context()
-	workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
-	}
-	if workspaceGeneralSetting.DisallowPasswordLogin {
-		return echo.NewHTTPError(http.StatusUnauthorized, "password login is deactivated").SetInternal(err)
-	}
-
-	signin := &SignIn{}
-	if err := json.NewDecoder(c.Request().Body).Decode(signin); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signin request").SetInternal(err)
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		Username: &signin.Username,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Incorrect login credentials, please try again")
-	}
-	if user == nil {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect login credentials, please try again")
-	} else if user.RowStatus == store.Archived {
-		return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with username %s", signin.Username))
-	}
-
-	// Compare the stored hashed password, with the hashed version of the password that was received.
-	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(signin.Password)); err != nil {
-		// If the two passwords don't match, return a 401 status.
-		return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect login credentials, please try again")
-	}
-
-	var expireAt time.Time
-	// Set cookie expiration to 100 years to make it persistent.
-	cookieExp := time.Now().AddDate(100, 0, 0)
-	if !signin.Remember {
-		expireAt = time.Now().Add(auth.AccessTokenDuration)
-		cookieExp = time.Now().Add(auth.CookieExpDuration)
-	}
-
-	accessToken, err := auth.GenerateAccessToken(user.Username, user.ID, expireAt, []byte(s.Secret))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to generate tokens, err: %s", err)).SetInternal(err)
-	}
-	if err := s.UpsertAccessTokenToStore(ctx, user, accessToken); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert access token, err: %s", err)).SetInternal(err)
-	}
-	setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
-	userMessage := convertUserFromStore(user)
-	return c.JSON(http.StatusOK, userMessage)
-}
-
-// SignOut godoc
-//
-//	@Summary	Sign-out from memos.
-//	@Tags		auth
-//	@Produce	json
-//	@Success	200	{boolean}	true	"Sign-out success"
-//	@Router		/api/v1/auth/signout [POST]
-func (s *APIV1Service) SignOut(c echo.Context) error {
-	accessToken := findAccessToken(c)
-	userID, _ := getUserIDFromAccessToken(accessToken, s.Secret)
-
-	err := removeAccessTokenAndCookies(c, s.Store, userID, accessToken)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to remove access token, err: %s", err)).SetInternal(err)
-	}
-
-	return c.JSON(http.StatusOK, true)
-}
-
-// SignUp godoc
-//
-//	@Summary	Sign-up to memos.
-//	@Tags		auth
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		SignUp		true	"Sign-up object"
-//	@Success	200		{object}	store.User	"User information"
-//	@Failure	400		{object}	nil			"Malformatted signup request | Failed to find users"
-//	@Failure	401		{object}	nil			"signup is disabled"
-//	@Failure	403		{object}	nil			"Forbidden"
-//	@Failure	404		{object}	nil			"Not found"
-//	@Failure	500		{object}	nil			"Failed to find system setting | Failed to unmarshal system setting allow signup | Failed to generate password hash | Failed to create user | Failed to generate tokens | Failed to create activity"
-//	@Router		/api/v1/auth/signup [POST]
-func (s *APIV1Service) SignUp(c echo.Context) error {
-	ctx := c.Request().Context()
-	signup := &SignUp{}
-	if err := json.NewDecoder(c.Request().Body).Decode(signup); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err)
-	}
-
-	hostUserType := store.RoleHost
-	existedHostUsers, err := s.Store.ListUsers(ctx, &store.FindUser{
-		Role: &hostUserType,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Failed to find users").SetInternal(err)
-	}
-	if !util.UIDMatcher.MatchString(strings.ToLower(signup.Username)) {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", signup.Username)).SetInternal(err)
-	}
-
-	userCreate := &store.User{
-		Username: signup.Username,
-		// The new signup user should be normal user by default.
-		Role:     store.RoleUser,
-		Nickname: signup.Username,
-	}
-	if len(existedHostUsers) == 0 {
-		// Change the default role to host if there is no host user.
-		userCreate.Role = store.RoleHost
-	} else {
-		workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
-		}
-		if workspaceGeneralSetting.DisallowSignup {
-			return echo.NewHTTPError(http.StatusUnauthorized, "signup is disabled").SetInternal(err)
-		}
-		if workspaceGeneralSetting.DisallowPasswordLogin {
-			return echo.NewHTTPError(http.StatusUnauthorized, "password login is deactivated").SetInternal(err)
-		}
-	}
-
-	passwordHash, err := bcrypt.GenerateFromPassword([]byte(signup.Password), bcrypt.DefaultCost)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
-	}
-
-	userCreate.PasswordHash = string(passwordHash)
-	user, err := s.Store.CreateUser(ctx, userCreate)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
-	}
-	accessToken, err := auth.GenerateAccessToken(user.Username, user.ID, time.Now().Add(auth.AccessTokenDuration), []byte(s.Secret))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to generate tokens, err: %s", err)).SetInternal(err)
-	}
-	if err := s.UpsertAccessTokenToStore(ctx, user, accessToken); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert access token, err: %s", err)).SetInternal(err)
-	}
-	cookieExp := time.Now().Add(auth.CookieExpDuration)
-	setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
-	userMessage := convertUserFromStore(user)
-	return c.JSON(http.StatusOK, userMessage)
-}
-
-func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store.User, accessToken string) error {
-	userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID)
-	if err != nil {
-		return errors.Wrap(err, "failed to get user access tokens")
-	}
-	userAccessToken := storepb.AccessTokensUserSetting_AccessToken{
-		AccessToken: accessToken,
-		Description: "Account sign in",
-	}
-	userAccessTokens = append(userAccessTokens, &userAccessToken)
-	if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
-		UserId: user.ID,
-		Key:    storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
-		Value: &storepb.UserSetting_AccessTokens{
-			AccessTokens: &storepb.AccessTokensUserSetting{
-				AccessTokens: userAccessTokens,
-			},
-		},
-	}); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert user setting, err: %s", err)).SetInternal(err)
-	}
-	return nil
-}
-
-// removeAccessTokenAndCookies removes the jwt token from the cookies.
-func removeAccessTokenAndCookies(c echo.Context, s *store.Store, userID int32, token string) error {
-	err := s.RemoveUserAccessToken(c.Request().Context(), userID, token)
-	if err != nil {
-		return err
-	}
-
-	cookieExp := time.Now().Add(-1 * time.Hour)
-	setTokenCookie(c, auth.AccessTokenCookieName, "", cookieExp)
-	return nil
-}
-
-// setTokenCookie sets the token to the cookie.
-func setTokenCookie(c echo.Context, name, token string, expiration time.Time) {
-	cookie := new(http.Cookie)
-	cookie.Name = name
-	cookie.Value = token
-	cookie.Expires = expiration
-	cookie.Path = "/"
-	// Http-only helps mitigate the risk of client side script accessing the protected cookie.
-	cookie.HttpOnly = true
-	cookie.SameSite = http.SameSiteStrictMode
-	c.SetCookie(cookie)
-}

programming_language: unknown
filename: common.go
change_type: ModificationType.DELETE
diff: @@ -1,15 +0,0 @@
-package v1
-
-// 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 {
-	return string(r)
-}

programming_language: unknown
filename: docs.go
change_type: ModificationType.DELETE
diff: @@ -1,3393 +0,0 @@
-// Code generated by swaggo/swag. DO NOT EDIT.
-
-package v1
-
-import "github.com/swaggo/swag"
-
-const docTemplate = `{
-    "schemes": {{ marshal .Schemes }},
-    "swagger": "2.0",
-    "info": {
-        "description": "{{escape .Description}}",
-        "title": "{{.Title}}",
-        "contact": {
-            "name": "API Support",
-            "url": "https://github.com/orgs/usememos/discussions"
-        },
-        "license": {
-            "name": "MIT License",
-            "url": "https://github.com/usememos/memos/blob/main/LICENSE"
-        },
-        "version": "{{.Version}}"
-    },
-    "host": "{{.Host}}",
-    "basePath": "{{.BasePath}}",
-    "paths": {
-        "/api/v1/auth/signin": {
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "auth"
-                ],
-                "summary": "Sign-in to memos.",
-                "parameters": [
-                    {
-                        "description": "Sign-in object",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignIn"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "User information",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted signin request"
-                    },
-                    "401": {
-                        "description": "Password login is deactivated | Incorrect login credentials, please try again"
-                    },
-                    "403": {
-                        "description": "User has been archived with username %s"
-                    },
-                    "500": {
-                        "description": "Failed to find system setting | Failed to unmarshal system setting | Incorrect login credentials, please try again | Failed to generate tokens | Failed to create activity"
-                    }
-                }
-            }
-        },
-        "/api/v1/auth/signin/sso": {
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "auth"
-                ],
-                "summary": "Sign-in to memos using SSO.",
-                "parameters": [
-                    {
-                        "description": "SSO sign-in object",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SSOSignIn"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "User information",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted signin request"
-                    },
-                    "401": {
-                        "description": "Access denied, identifier does not match the filter."
-                    },
-                    "403": {
-                        "description": "User has been archived with username {username}"
-                    },
-                    "404": {
-                        "description": "Identity provider not found"
-                    },
-                    "500": {
-                        "description": "Failed to find identity provider | Failed to create identity provider instance | Failed to exchange token | Failed to get user info | Failed to compile identifier filter | Incorrect login credentials, please try again | Failed to generate random password | Failed to generate password hash | Failed to create user | Failed to generate tokens | Failed to create activity"
-                    }
-                }
-            }
-        },
-        "/api/v1/auth/signout": {
-            "post": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "auth"
-                ],
-                "summary": "Sign-out from memos.",
-                "responses": {
-                    "200": {
-                        "description": "Sign-out success",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    }
-                }
-            }
-        },
-        "/api/v1/auth/signup": {
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "auth"
-                ],
-                "summary": "Sign-up to memos.",
-                "parameters": [
-                    {
-                        "description": "Sign-up object",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.SignUp"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "User information",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted signup request | Failed to find users"
-                    },
-                    "401": {
-                        "description": "signup is disabled"
-                    },
-                    "403": {
-                        "description": "Forbidden"
-                    },
-                    "404": {
-                        "description": "Not found"
-                    },
-                    "500": {
-                        "description": "Failed to find system setting | Failed to unmarshal system setting allow signup | Failed to generate password hash | Failed to create user | Failed to generate tokens | Failed to create activity"
-                    }
-                }
-            }
-        },
-        "/api/v1/idp": {
-            "get": {
-                "description": "*clientSecret is only available for host user",
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "idp"
-                ],
-                "summary": "Get a list of identity providers",
-                "responses": {
-                    "200": {
-                        "description": "List of available identity providers",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/api_v1.IdentityProvider"
-                            }
-                        }
-                    },
-                    "500": {
-                        "description": "Failed to find identity provider list | Failed to find user"
-                    }
-                }
-            },
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "idp"
-                ],
-                "summary": "Create Identity Provider",
-                "parameters": [
-                    {
-                        "description": "Identity provider information",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.CreateIdentityProviderRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Identity provider information",
-                        "schema": {
-                            "$ref": "#/definitions/store.IdentityProvider"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted post identity provider request"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to create identity provider"
-                    }
-                }
-            }
-        },
-        "/api/v1/idp/{idpId}": {
-            "get": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "idp"
-                ],
-                "summary": "Get an identity provider by ID",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Identity provider ID",
-                        "name": "idpId",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Requested identity provider",
-                        "schema": {
-                            "$ref": "#/definitions/store.IdentityProvider"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "404": {
-                        "description": "Identity provider not found"
-                    },
-                    "500": {
-                        "description": "Failed to find identity provider list | Failed to find user"
-                    }
-                }
-            },
-            "delete": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "idp"
-                ],
-                "summary": "Delete an identity provider by ID",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Identity Provider ID",
-                        "name": "idpId",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Identity Provider deleted",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Malformatted patch identity provider request"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to patch identity provider"
-                    }
-                }
-            },
-            "patch": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "idp"
-                ],
-                "summary": "Update an identity provider by ID",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Identity Provider ID",
-                        "name": "idpId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "description": "Patched identity provider information",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.UpdateIdentityProviderRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Patched identity provider",
-                        "schema": {
-                            "$ref": "#/definitions/store.IdentityProvider"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Malformatted patch identity provider request"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to patch identity provider"
-                    }
-                }
-            }
-        },
-        "/api/v1/memo": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo"
-                ],
-                "summary": "Get a list of 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": "Memo list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/store.Memo"
-                            }
-                        }
-                    },
-                    "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"
-                    }
-                }
-            },
-            "post": {
-                "description": "Visibility can be PUBLIC, PROTECTED or PRIVATE\n*You should omit fields to use their default values",
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo"
-                ],
-                "summary": "Create a memo",
-                "parameters": [
-                    {
-                        "description": "Request object.",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateMemoRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Stored memo",
-                        "schema": {
-                            "$ref": "#/definitions/store.Memo"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted post memo request | Content size overflow, up to 1MB"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "404": {
-                        "description": "User not found | Memo not found: %d"
-                    },
-                    "500": {
-                        "description": "Failed to find user setting | Failed to unmarshal user setting value | Failed to find system setting | Failed to unmarshal system setting | Failed to find user | Failed to create memo | Failed to create activity | Failed to upsert memo resource | Failed to upsert memo relation | Failed to compose memo | Failed to compose memo response"
-                    }
-                }
-            }
-        },
-        "/api/v1/memo/all": {
-            "get": {
-                "description": "This should also list protected memos if the user is logged in\nAuthentication is optional",
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo"
-                ],
-                "summary": "Get a list of public memos matching optional filters",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Limit",
-                        "name": "limit",
-                        "in": "query"
-                    },
-                    {
-                        "type": "integer",
-                        "description": "Offset",
-                        "name": "offset",
-                        "in": "query"
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/store.Memo"
-                            }
-                        }
-                    },
-                    "500": {
-                        "description": "Failed to get memo display with updated ts setting value | Failed to fetch all memo list | Failed to compose memo response"
-                    }
-                }
-            }
-        },
-        "/api/v1/memo/stats": {
-            "get": {
-                "description": "Used to generate the heatmap",
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo"
-                ],
-                "summary": "Get memo stats by creator ID or username",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Creator ID",
-                        "name": "creatorId",
-                        "in": "query"
-                    },
-                    {
-                        "type": "string",
-                        "description": "Creator username",
-                        "name": "creatorUsername",
-                        "in": "query"
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo createdTs list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "integer"
-                            }
-                        }
-                    },
-                    "400": {
-                        "description": "Missing user id to find memo"
-                    },
-                    "500": {
-                        "description": "Failed to get memo display with updated ts setting value | Failed to find memo list | Failed to compose memo response"
-                    }
-                }
-            }
-        },
-        "/api/v1/memo/{memoId}": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo"
-                ],
-                "summary": "Get memo by ID",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Memo ID",
-                        "name": "memoId",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/store.Memo"
-                            }
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "403": {
-                        "description": "this memo is private only | this memo is protected, missing user in session"
-                    },
-                    "404": {
-                        "description": "Memo not found: %d"
-                    },
-                    "500": {
-                        "description": "Failed to find memo by ID: %v | Failed to compose memo response"
-                    }
-                }
-            },
-            "delete": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo"
-                ],
-                "summary": "Delete memo by ID",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Memo ID to delete",
-                        "name": "memoId",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo deleted",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "404": {
-                        "description": "Memo not found: %d"
-                    },
-                    "500": {
-                        "description": "Failed to find memo | Failed to delete memo ID: %v"
-                    }
-                }
-            },
-            "patch": {
-                "description": "Visibility can be PUBLIC, PROTECTED or PRIVATE\n*You should omit fields to use their default values",
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo"
-                ],
-                "summary": "Update a memo",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "ID of memo to update",
-                        "name": "memoId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "description": "Patched object.",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.PatchMemoRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Stored memo",
-                        "schema": {
-                            "$ref": "#/definitions/store.Memo"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Malformatted patch memo request | Content size overflow, up to 1MB"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "404": {
-                        "description": "Memo not found: %d"
-                    },
-                    "500": {
-                        "description": "Failed to find memo | Failed to patch memo | Failed to upsert memo resource | Failed to delete memo resource | Failed to compose memo response"
-                    }
-                }
-            }
-        },
-        "/api/v1/memo/{memoId}/organizer": {
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo-organizer"
-                ],
-                "summary": "Organize memo (pin/unpin)",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "ID of memo to organize",
-                        "name": "memoId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "description": "Memo organizer object",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo information",
-                        "schema": {
-                            "$ref": "#/definitions/store.Memo"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Malformatted post memo organizer request"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "404": {
-                        "description": "Memo not found: %v"
-                    },
-                    "500": {
-                        "description": "Failed to find memo | Failed to upsert memo organizer | Failed to find memo by ID: %v | Failed to compose memo response"
-                    }
-                }
-            }
-        },
-        "/api/v1/memo/{memoId}/relation": {
-            "get": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo-relation"
-                ],
-                "summary": "Get a list of Memo Relations",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "ID of memo to find relations",
-                        "name": "memoId",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo relation information list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/store.MemoRelation"
-                            }
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s"
-                    },
-                    "500": {
-                        "description": "Failed to list memo relations"
-                    }
-                }
-            },
-            "post": {
-                "description": "Create a relation between two memos",
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo-relation"
-                ],
-                "summary": "Create Memo Relation",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "ID of memo to relate",
-                        "name": "memoId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "description": "Memo relation object",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo relation information",
-                        "schema": {
-                            "$ref": "#/definitions/store.MemoRelation"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Malformatted post memo relation request"
-                    },
-                    "500": {
-                        "description": "Failed to upsert memo relation"
-                    }
-                }
-            }
-        },
-        "/api/v1/memo/{memoId}/relation/{relatedMemoId}/type/{relationType}": {
-            "delete": {
-                "description": "Removes a relation between two memos",
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "memo-relation"
-                ],
-                "summary": "Delete a Memo Relation",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "ID of memo to find relations",
-                        "name": "memoId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "type": "integer",
-                        "description": "ID of memo to remove relation to",
-                        "name": "relatedMemoId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "enum": [
-                            "REFERENCE",
-                            "COMMENT"
-                        ],
-                        "type": "string",
-                        "description": "Type of relation to remove",
-                        "name": "relationType",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Memo relation deleted",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "400": {
-                        "description": "Memo ID is not a number: %s | Related memo ID is not a number: %s"
-                    },
-                    "500": {
-                        "description": "Failed to delete memo relation"
-                    }
-                }
-            }
-        },
-        "/api/v1/ping": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "system"
-                ],
-                "summary": "Ping the system",
-                "responses": {
-                    "200": {
-                        "description": "If succeed to ping the system",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    }
-                }
-            }
-        },
-        "/api/v1/resource": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "resource"
-                ],
-                "summary": "Get a list of resources",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Limit",
-                        "name": "limit",
-                        "in": "query"
-                    },
-                    {
-                        "type": "integer",
-                        "description": "Offset",
-                        "name": "offset",
-                        "in": "query"
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Resource list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/store.Resource"
-                            }
-                        }
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "500": {
-                        "description": "Failed to fetch resource list"
-                    }
-                }
-            },
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "resource"
-                ],
-                "summary": "Create resource",
-                "parameters": [
-                    {
-                        "description": "Request object.",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.CreateResourceRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Created resource",
-                        "schema": {
-                            "$ref": "#/definitions/store.Resource"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted post resource request | Invalid external link | Invalid external link scheme | Failed to request %s | Failed to read %s | Failed to read mime from %s"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "500": {
-                        "description": "Failed to save resource | Failed to create resource | Failed to create activity"
-                    }
-                }
-            }
-        },
-        "/api/v1/resource/blob": {
-            "post": {
-                "consumes": [
-                    "multipart/form-data"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "resource"
-                ],
-                "summary": "Upload resource",
-                "parameters": [
-                    {
-                        "type": "file",
-                        "description": "File to upload",
-                        "name": "file",
-                        "in": "formData",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Created resource",
-                        "schema": {
-                            "$ref": "#/definitions/store.Resource"
-                        }
-                    },
-                    "400": {
-                        "description": "Upload file not found | File size exceeds allowed limit of %d MiB | Failed to parse upload data"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "500": {
-                        "description": "Failed to get uploading file | Failed to open file | Failed to save resource | Failed to create resource | Failed to create activity"
-                    }
-                }
-            }
-        },
-        "/api/v1/resource/{resourceId}": {
-            "delete": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "resource"
-                ],
-                "summary": "Delete a resource",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Resource ID",
-                        "name": "resourceId",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Resource deleted",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "404": {
-                        "description": "Resource not found: %d"
-                    },
-                    "500": {
-                        "description": "Failed to find resource | Failed to delete resource"
-                    }
-                }
-            },
-            "patch": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "resource"
-                ],
-                "summary": "Update a resource",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Resource ID",
-                        "name": "resourceId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "description": "Patch resource request",
-                        "name": "patch",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.UpdateResourceRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Updated resource",
-                        "schema": {
-                            "$ref": "#/definitions/store.Resource"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Malformatted patch resource request"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "404": {
-                        "description": "Resource not found: %d"
-                    },
-                    "500": {
-                        "description": "Failed to find resource | Failed to patch resource"
-                    }
-                }
-            }
-        },
-        "/api/v1/status": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "system"
-                ],
-                "summary": "Get system GetSystemStatus",
-                "responses": {
-                    "200": {
-                        "description": "System GetSystemStatus",
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.SystemStatus"
-                        }
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find host user | Failed to find system setting list | Failed to unmarshal system setting customized profile value"
-                    }
-                }
-            }
-        },
-        "/api/v1/storage": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "storage"
-                ],
-                "summary": "Get a list of storages",
-                "responses": {
-                    "200": {
-                        "description": "List of storages",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/store.Storage"
-                            }
-                        }
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to convert storage"
-                    }
-                }
-            },
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "storage"
-                ],
-                "summary": "Create storage",
-                "parameters": [
-                    {
-                        "description": "Request object.",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CreateStorageRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Created storage",
-                        "schema": {
-                            "$ref": "#/definitions/store.Storage"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted post storage request"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to create storage | Failed to convert storage"
-                    }
-                }
-            }
-        },
-        "/api/v1/storage/{storageId}": {
-            "delete": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "storage"
-                ],
-                "summary": "Delete a storage",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Storage ID",
-                        "name": "storageId",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Storage deleted",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Storage service %d is using"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to find storage | Failed to unmarshal storage service id | Failed to delete storage"
-                    }
-                }
-            },
-            "patch": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "storage"
-                ],
-                "summary": "Update a storage",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "Storage ID",
-                        "name": "storageId",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "description": "Patch request",
-                        "name": "patch",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpdateStorageRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Updated resource",
-                        "schema": {
-                            "$ref": "#/definitions/store.Storage"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Malformatted patch storage request | Malformatted post storage request"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to patch storage | Failed to convert storage"
-                    }
-                }
-            }
-        },
-        "/api/v1/system/setting": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "system-setting"
-                ],
-                "summary": "Get a list of system settings",
-                "responses": {
-                    "200": {
-                        "description": "System setting list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/api_v1.SystemSetting"
-                            }
-                        }
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to find system setting list"
-                    }
-                }
-            },
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "system-setting"
-                ],
-                "summary": "Create system setting",
-                "parameters": [
-                    {
-                        "description": "Request object.",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.UpsertSystemSettingRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "400": {
-                        "description": "Malformatted post system setting request | invalid system setting"
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "403": {
-                        "description": "Cannot disable passwords if no SSO identity provider is configured."
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to upsert system setting"
-                    }
-                }
-            }
-        },
-        "/api/v1/system/vacuum": {
-            "post": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "system"
-                ],
-                "summary": "Vacuum the database",
-                "responses": {
-                    "200": {
-                        "description": "Database vacuumed",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "401": {
-                        "description": "Missing user in session | Unauthorized"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to ExecVacuum database"
-                    }
-                }
-            }
-        },
-        "/api/v1/tag": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "tag"
-                ],
-                "summary": "Get a list of tags",
-                "responses": {
-                    "200": {
-                        "description": "Tag list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string"
-                            }
-                        }
-                    },
-                    "400": {
-                        "description": "Missing user id to find tag"
-                    },
-                    "500": {
-                        "description": "Failed to find tag list"
-                    }
-                }
-            },
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "tag"
-                ],
-                "summary": "Create a tag",
-                "parameters": [
-                    {
-                        "description": "Request object.",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertTagRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Created tag name",
-                        "schema": {
-                            "type": "string"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted post tag request | Tag name shouldn't be empty"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "500": {
-                        "description": "Failed to upsert tag | Failed to create activity"
-                    }
-                }
-            }
-        },
-        "/api/v1/tag/delete": {
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "tag"
-                ],
-                "summary": "Delete a tag",
-                "parameters": [
-                    {
-                        "description": "Request object.",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.DeleteTagRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Tag deleted",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted post tag request | Tag name shouldn't be empty"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "500": {
-                        "description": "Failed to delete tag name: %v"
-                    }
-                }
-            }
-        },
-        "/api/v1/tag/suggestion": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "tag"
-                ],
-                "summary": "Get a list of tags suggested from other memos contents",
-                "responses": {
-                    "200": {
-                        "description": "Tag list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string"
-                            }
-                        }
-                    },
-                    "400": {
-                        "description": "Missing user session"
-                    },
-                    "500": {
-                        "description": "Failed to find memo list | Failed to find tag list"
-                    }
-                }
-            }
-        },
-        "/api/v1/user": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "user"
-                ],
-                "summary": "Get a list of users",
-                "responses": {
-                    "200": {
-                        "description": "User list",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "$ref": "#/definitions/store.User"
-                            }
-                        }
-                    },
-                    "500": {
-                        "description": "Failed to fetch user list"
-                    }
-                }
-            },
-            "post": {
-                "consumes": [
-                    "application/json"
-                ],
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "user"
-                ],
-                "summary": "Create a user",
-                "parameters": [
-                    {
-                        "description": "Request object",
-                        "name": "body",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.CreateUserRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Created user",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted post user request | Invalid user create format"
-                    },
-                    "401": {
-                        "description": "Missing auth session | Unauthorized to create user"
-                    },
-                    "403": {
-                        "description": "Could not create host user"
-                    },
-                    "500": {
-                        "description": "Failed to find user by id | Failed to generate password hash | Failed to create user | Failed to create activity"
-                    }
-                }
-            }
-        },
-        "/api/v1/user/me": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "user"
-                ],
-                "summary": "Get current user",
-                "responses": {
-                    "200": {
-                        "description": "Current user",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "401": {
-                        "description": "Missing auth session"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to find userSettingList"
-                    }
-                }
-            }
-        },
-        "/api/v1/user/name/{username}": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "user"
-                ],
-                "summary": "Get user by username",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "Username",
-                        "name": "username",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Requested user",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "404": {
-                        "description": "User not found"
-                    },
-                    "500": {
-                        "description": "Failed to find user"
-                    }
-                }
-            }
-        },
-        "/api/v1/user/{id}": {
-            "get": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "user"
-                ],
-                "summary": "Get user by id",
-                "parameters": [
-                    {
-                        "type": "integer",
-                        "description": "User ID",
-                        "name": "id",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Requested user",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "400": {
-                        "description": "Malformatted user id"
-                    },
-                    "404": {
-                        "description": "User not found"
-                    },
-                    "500": {
-                        "description": "Failed to find user"
-                    }
-                }
-            },
-            "delete": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "user"
-                ],
-                "summary": "Delete a user",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "User ID",
-                        "name": "id",
-                        "in": "path",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "User deleted",
-                        "schema": {
-                            "type": "boolean"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Current session user not found with ID: %d"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "403": {
-                        "description": "Unauthorized to delete user"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to delete user"
-                    }
-                }
-            },
-            "patch": {
-                "produces": [
-                    "application/json"
-                ],
-                "tags": [
-                    "user"
-                ],
-                "summary": "Update a user",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "User ID",
-                        "name": "id",
-                        "in": "path",
-                        "required": true
-                    },
-                    {
-                        "description": "Patch request",
-                        "name": "patch",
-                        "in": "body",
-                        "required": true,
-                        "schema": {
-                            "$ref": "#/definitions/api_v1.UpdateUserRequest"
-                        }
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Updated user",
-                        "schema": {
-                            "$ref": "#/definitions/store.User"
-                        }
-                    },
-                    "400": {
-                        "description": "ID is not a number: %s | Current session user not found with ID: %d | Malformatted patch user request | Invalid update user request"
-                    },
-                    "401": {
-                        "description": "Missing user in session"
-                    },
-                    "403": {
-                        "description": "Unauthorized to update user"
-                    },
-                    "500": {
-                        "description": "Failed to find user | Failed to generate password hash | Failed to patch user | Failed to find userSettingList"
-                    }
-                }
-            }
-        },
-        "/o/get/GetImage": {
-            "get": {
-                "produces": [
-                    "GetImage/*"
-                ],
-                "tags": [
-                    "image-url"
-                ],
-                "summary": "Get GetImage from URL",
-                "parameters": [
-                    {
-                        "type": "string",
-                        "description": "Image url",
-                        "name": "url",
-                        "in": "query",
-                        "required": true
-                    }
-                ],
-                "responses": {
-                    "200": {
-                        "description": "Image"
-                    },
-                    "400": {
-                        "description": "Missing GetImage url | Wrong url | Failed to get GetImage url: %s"
-                    },
-                    "500": {
-                        "description": "Failed to write GetImage blob"
-                    }
-                }
-            }
-        }
-    },
-    "definitions": {
-        "api_v1.CreateIdentityProviderRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/api_v1.IdentityProviderConfig"
-                },
-                "identifierFilter": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/api_v1.IdentityProviderType"
-                }
-            }
-        },
-        "api_v1.CreateMemoRequest": {
-            "type": "object",
-            "properties": {
-                "content": {
-                    "type": "string"
-                },
-                "createdTs": {
-                    "type": "integer"
-                },
-                "relationList": {
-                    "type": "array",
-                    "items": {
-                        "$ref": "#/definitions/api_v1.UpsertMemoRelationRequest"
-                    }
-                },
-                "resourceIdList": {
-                    "description": "Related fields",
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
-                },
-                "visibility": {
-                    "description": "Domain specific fields",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/api_v1.Visibility"
-                        }
-                    ]
-                }
-            }
-        },
-        "api_v1.CreateResourceRequest": {
-            "type": "object",
-            "properties": {
-                "externalLink": {
-                    "type": "string"
-                },
-                "filename": {
-                    "type": "string"
-                },
-                "type": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.CreateStorageRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/api_v1.StorageConfig"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/api_v1.StorageType"
-                }
-            }
-        },
-        "api_v1.CreateUserRequest": {
-            "type": "object",
-            "properties": {
-                "email": {
-                    "type": "string"
-                },
-                "nickname": {
-                    "type": "string"
-                },
-                "password": {
-                    "type": "string"
-                },
-                "role": {
-                    "$ref": "#/definitions/api_v1.Role"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.CustomizedProfile": {
-            "type": "object",
-            "properties": {
-                "appearance": {
-                    "description": "Appearance is the server default appearance.",
-                    "type": "string"
-                },
-                "description": {
-                    "description": "Description is the server description.",
-                    "type": "string"
-                },
-                "locale": {
-                    "description": "Locale is the server default locale.",
-                    "type": "string"
-                },
-                "logoUrl": {
-                    "description": "LogoURL is the url of logo image.",
-                    "type": "string"
-                },
-                "name": {
-                    "description": "Name is the server name, default is ` + "`" + `memos` + "`" + `",
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.DeleteTagRequest": {
-            "type": "object",
-            "properties": {
-                "name": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.FieldMapping": {
-            "type": "object",
-            "properties": {
-                "displayName": {
-                    "type": "string"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "identifier": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.IdentityProvider": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/api_v1.IdentityProviderConfig"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "identifierFilter": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/api_v1.IdentityProviderType"
-                }
-            }
-        },
-        "api_v1.IdentityProviderConfig": {
-            "type": "object",
-            "properties": {
-                "oauth2Config": {
-                    "$ref": "#/definitions/api_v1.IdentityProviderOAuth2Config"
-                }
-            }
-        },
-        "api_v1.IdentityProviderOAuth2Config": {
-            "type": "object",
-            "properties": {
-                "authUrl": {
-                    "type": "string"
-                },
-                "clientId": {
-                    "type": "string"
-                },
-                "clientSecret": {
-                    "type": "string"
-                },
-                "fieldMapping": {
-                    "$ref": "#/definitions/api_v1.FieldMapping"
-                },
-                "scopes": {
-                    "type": "array",
-                    "items": {
-                        "type": "string"
-                    }
-                },
-                "tokenUrl": {
-                    "type": "string"
-                },
-                "userInfoUrl": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.IdentityProviderType": {
-            "type": "string",
-            "enum": [
-                "OAUTH2"
-            ],
-            "x-enum-varnames": [
-                "IdentityProviderOAuth2Type"
-            ]
-        },
-        "api_v1.MemoRelationType": {
-            "type": "string",
-            "enum": [
-                "REFERENCE",
-                "COMMENT"
-            ],
-            "x-enum-varnames": [
-                "MemoRelationReference",
-                "MemoRelationComment"
-            ]
-        },
-        "api_v1.PatchMemoRequest": {
-            "type": "object",
-            "properties": {
-                "content": {
-                    "description": "Domain specific fields",
-                    "type": "string"
-                },
-                "createdTs": {
-                    "description": "Standard fields",
-                    "type": "integer"
-                },
-                "relationList": {
-                    "type": "array",
-                    "items": {
-                        "$ref": "#/definitions/api_v1.UpsertMemoRelationRequest"
-                    }
-                },
-                "resourceIdList": {
-                    "description": "Related fields",
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
-                },
-                "rowStatus": {
-                    "$ref": "#/definitions/api_v1.RowStatus"
-                },
-                "updatedTs": {
-                    "type": "integer"
-                },
-                "visibility": {
-                    "$ref": "#/definitions/api_v1.Visibility"
-                }
-            }
-        },
-        "api_v1.Role": {
-            "type": "string",
-            "enum": [
-                "HOST",
-                "ADMIN",
-                "USER"
-            ],
-            "x-enum-varnames": [
-                "RoleHost",
-                "RoleAdmin",
-                "RoleUser"
-            ]
-        },
-        "api_v1.RowStatus": {
-            "type": "string",
-            "enum": [
-                "NORMAL",
-                "ARCHIVED"
-            ],
-            "x-enum-varnames": [
-                "Normal",
-                "Archived"
-            ]
-        },
-        "api_v1.SSOSignIn": {
-            "type": "object",
-            "properties": {
-                "code": {
-                    "type": "string"
-                },
-                "identityProviderId": {
-                    "type": "integer"
-                },
-                "redirectUri": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.SignIn": {
-            "type": "object",
-            "properties": {
-                "password": {
-                    "type": "string"
-                },
-                "remember": {
-                    "type": "boolean"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.SignUp": {
-            "type": "object",
-            "properties": {
-                "password": {
-                    "type": "string"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.StorageConfig": {
-            "type": "object",
-            "properties": {
-                "s3Config": {
-                    "$ref": "#/definitions/api_v1.StorageS3Config"
-                }
-            }
-        },
-        "api_v1.StorageS3Config": {
-            "type": "object",
-            "properties": {
-                "accessKey": {
-                    "type": "string"
-                },
-                "bucket": {
-                    "type": "string"
-                },
-                "endPoint": {
-                    "type": "string"
-                },
-                "path": {
-                    "type": "string"
-                },
-                "presign": {
-                    "type": "boolean"
-                },
-                "region": {
-                    "type": "string"
-                },
-                "secretKey": {
-                    "type": "string"
-                },
-                "urlPrefix": {
-                    "type": "string"
-                },
-                "urlSuffix": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.StorageType": {
-            "type": "string",
-            "enum": [
-                "S3"
-            ],
-            "x-enum-varnames": [
-                "StorageS3"
-            ]
-        },
-        "api_v1.SystemSetting": {
-            "type": "object",
-            "properties": {
-                "description": {
-                    "type": "string"
-                },
-                "name": {
-                    "$ref": "#/definitions/api_v1.SystemSettingName"
-                },
-                "value": {
-                    "description": "Value is a JSON string with basic value.",
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.SystemSettingName": {
-            "type": "string",
-            "enum": [
-                "server-id",
-                "secret-session",
-                "disable-public-memos",
-                "max-upload-size-mib",
-                "customized-profile",
-                "storage-service-id",
-                "local-storage-path",
-                "telegram-bot-token",
-                "memo-display-with-updated-ts"
-            ],
-            "x-enum-varnames": [
-                "SystemSettingServerIDName",
-                "SystemSettingSecretSessionName",
-                "SystemSettingDisablePublicMemosName",
-                "SystemSettingMaxUploadSizeMiBName",
-                "SystemSettingCustomizedProfileName",
-                "SystemSettingStorageServiceIDName",
-                "SystemSettingLocalStoragePathName",
-                "SystemSettingTelegramBotTokenName",
-                "SystemSettingMemoDisplayWithUpdatedTsName"
-            ]
-        },
-        "api_v1.SystemStatus": {
-            "type": "object",
-            "properties": {
-                "additionalScript": {
-                    "description": "Additional script.",
-                    "type": "string"
-                },
-                "additionalStyle": {
-                    "description": "Additional style.",
-                    "type": "string"
-                },
-                "allowSignUp": {
-                    "description": "System settings\nAllow sign up.",
-                    "type": "boolean"
-                },
-                "customizedProfile": {
-                    "description": "Customized server profile, including server name and external url.",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/api_v1.CustomizedProfile"
-                        }
-                    ]
-                },
-                "dbSize": {
-                    "type": "integer"
-                },
-                "disablePasswordLogin": {
-                    "description": "Disable password login.",
-                    "type": "boolean"
-                },
-                "disablePublicMemos": {
-                    "description": "Disable public memos.",
-                    "type": "boolean"
-                },
-                "host": {
-                    "$ref": "#/definitions/api_v1.User"
-                },
-                "localStoragePath": {
-                    "description": "Local storage path.",
-                    "type": "string"
-                },
-                "maxUploadSizeMiB": {
-                    "description": "Max upload size.",
-                    "type": "integer"
-                },
-                "memoDisplayWithUpdatedTs": {
-                    "description": "Memo display with updated timestamp.",
-                    "type": "boolean"
-                },
-                "profile": {
-                    "$ref": "#/definitions/profile.Profile"
-                },
-                "storageServiceId": {
-                    "description": "Storage service ID.",
-                    "type": "integer"
-                }
-            }
-        },
-        "api_v1.UpdateIdentityProviderRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/api_v1.IdentityProviderConfig"
-                },
-                "identifierFilter": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/api_v1.IdentityProviderType"
-                }
-            }
-        },
-        "api_v1.UpdateResourceRequest": {
-            "type": "object",
-            "properties": {
-                "filename": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.UpdateStorageRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/api_v1.StorageConfig"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/api_v1.StorageType"
-                }
-            }
-        },
-        "api_v1.UpdateUserRequest": {
-            "type": "object",
-            "properties": {
-                "avatarUrl": {
-                    "type": "string"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "nickname": {
-                    "type": "string"
-                },
-                "password": {
-                    "type": "string"
-                },
-                "rowStatus": {
-                    "$ref": "#/definitions/api_v1.RowStatus"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.UpsertMemoOrganizerRequest": {
-            "type": "object",
-            "properties": {
-                "pinned": {
-                    "type": "boolean"
-                }
-            }
-        },
-        "api_v1.UpsertMemoRelationRequest": {
-            "type": "object",
-            "properties": {
-                "relatedMemoId": {
-                    "type": "integer"
-                },
-                "type": {
-                    "$ref": "#/definitions/api_v1.MemoRelationType"
-                }
-            }
-        },
-        "api_v1.UpsertSystemSettingRequest": {
-            "type": "object",
-            "properties": {
-                "description": {
-                    "type": "string"
-                },
-                "name": {
-                    "$ref": "#/definitions/api_v1.SystemSettingName"
-                },
-                "value": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.UpsertTagRequest": {
-            "type": "object",
-            "properties": {
-                "name": {
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.User": {
-            "type": "object",
-            "properties": {
-                "avatarUrl": {
-                    "type": "string"
-                },
-                "createdTs": {
-                    "type": "integer"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "nickname": {
-                    "type": "string"
-                },
-                "role": {
-                    "$ref": "#/definitions/api_v1.Role"
-                },
-                "rowStatus": {
-                    "description": "Standard fields",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/api_v1.RowStatus"
-                        }
-                    ]
-                },
-                "updatedTs": {
-                    "type": "integer"
-                },
-                "username": {
-                    "description": "Domain specific fields",
-                    "type": "string"
-                }
-            }
-        },
-        "api_v1.Visibility": {
-            "type": "string",
-            "enum": [
-                "PUBLIC",
-                "PROTECTED",
-                "PRIVATE"
-            ],
-            "x-enum-varnames": [
-                "Public",
-                "Protected",
-                "Private"
-            ]
-        },
-        "github_com_usememos_memos_api_v1.CreateIdentityProviderRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProviderConfig"
-                },
-                "identifierFilter": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProviderType"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.CreateMemoRequest": {
-            "type": "object",
-            "properties": {
-                "content": {
-                    "type": "string"
-                },
-                "createdTs": {
-                    "type": "integer"
-                },
-                "relationList": {
-                    "type": "array",
-                    "items": {
-                        "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest"
-                    }
-                },
-                "resourceIdList": {
-                    "description": "Related fields",
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
-                },
-                "visibility": {
-                    "description": "Domain specific fields",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.Visibility"
-                        }
-                    ]
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.CreateResourceRequest": {
-            "type": "object",
-            "properties": {
-                "externalLink": {
-                    "type": "string"
-                },
-                "filename": {
-                    "type": "string"
-                },
-                "type": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.CreateStorageRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.StorageConfig"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.StorageType"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.CreateUserRequest": {
-            "type": "object",
-            "properties": {
-                "email": {
-                    "type": "string"
-                },
-                "nickname": {
-                    "type": "string"
-                },
-                "password": {
-                    "type": "string"
-                },
-                "role": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.Role"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.CustomizedProfile": {
-            "type": "object",
-            "properties": {
-                "appearance": {
-                    "description": "Appearance is the server default appearance.",
-                    "type": "string"
-                },
-                "description": {
-                    "description": "Description is the server description.",
-                    "type": "string"
-                },
-                "locale": {
-                    "description": "Locale is the server default locale.",
-                    "type": "string"
-                },
-                "logoUrl": {
-                    "description": "LogoURL is the url of logo image.",
-                    "type": "string"
-                },
-                "name": {
-                    "description": "Name is the server name, default is ` + "`" + `memos` + "`" + `",
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.DeleteTagRequest": {
-            "type": "object",
-            "properties": {
-                "name": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.FieldMapping": {
-            "type": "object",
-            "properties": {
-                "displayName": {
-                    "type": "string"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "identifier": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.IdentityProvider": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProviderConfig"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "identifierFilter": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProviderType"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.IdentityProviderConfig": {
-            "type": "object",
-            "properties": {
-                "oauth2Config": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProviderOAuth2Config"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.IdentityProviderOAuth2Config": {
-            "type": "object",
-            "properties": {
-                "authUrl": {
-                    "type": "string"
-                },
-                "clientId": {
-                    "type": "string"
-                },
-                "clientSecret": {
-                    "type": "string"
-                },
-                "fieldMapping": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.FieldMapping"
-                },
-                "scopes": {
-                    "type": "array",
-                    "items": {
-                        "type": "string"
-                    }
-                },
-                "tokenUrl": {
-                    "type": "string"
-                },
-                "userInfoUrl": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.IdentityProviderType": {
-            "type": "string",
-            "enum": [
-                "OAUTH2"
-            ],
-            "x-enum-varnames": [
-                "IdentityProviderOAuth2Type"
-            ]
-        },
-        "github_com_usememos_memos_api_v1.MemoRelationType": {
-            "type": "string",
-            "enum": [
-                "REFERENCE",
-                "COMMENT"
-            ],
-            "x-enum-varnames": [
-                "MemoRelationReference",
-                "MemoRelationComment"
-            ]
-        },
-        "github_com_usememos_memos_api_v1.PatchMemoRequest": {
-            "type": "object",
-            "properties": {
-                "content": {
-                    "description": "Domain specific fields",
-                    "type": "string"
-                },
-                "createdTs": {
-                    "description": "Standard fields",
-                    "type": "integer"
-                },
-                "relationList": {
-                    "type": "array",
-                    "items": {
-                        "$ref": "#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest"
-                    }
-                },
-                "resourceIdList": {
-                    "description": "Related fields",
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
-                },
-                "rowStatus": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.RowStatus"
-                },
-                "updatedTs": {
-                    "type": "integer"
-                },
-                "visibility": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.Visibility"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.Role": {
-            "type": "string",
-            "enum": [
-                "HOST",
-                "ADMIN",
-                "USER"
-            ],
-            "x-enum-varnames": [
-                "RoleHost",
-                "RoleAdmin",
-                "RoleUser"
-            ]
-        },
-        "github_com_usememos_memos_api_v1.RowStatus": {
-            "type": "string",
-            "enum": [
-                "NORMAL",
-                "ARCHIVED"
-            ],
-            "x-enum-varnames": [
-                "Normal",
-                "Archived"
-            ]
-        },
-        "github_com_usememos_memos_api_v1.SSOSignIn": {
-            "type": "object",
-            "properties": {
-                "code": {
-                    "type": "string"
-                },
-                "identityProviderId": {
-                    "type": "integer"
-                },
-                "redirectUri": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.SignIn": {
-            "type": "object",
-            "properties": {
-                "password": {
-                    "type": "string"
-                },
-                "remember": {
-                    "type": "boolean"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.SignUp": {
-            "type": "object",
-            "properties": {
-                "password": {
-                    "type": "string"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.StorageConfig": {
-            "type": "object",
-            "properties": {
-                "s3Config": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.StorageS3Config"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.StorageS3Config": {
-            "type": "object",
-            "properties": {
-                "accessKey": {
-                    "type": "string"
-                },
-                "bucket": {
-                    "type": "string"
-                },
-                "endPoint": {
-                    "type": "string"
-                },
-                "path": {
-                    "type": "string"
-                },
-                "presign": {
-                    "type": "boolean"
-                },
-                "region": {
-                    "type": "string"
-                },
-                "secretKey": {
-                    "type": "string"
-                },
-                "urlPrefix": {
-                    "type": "string"
-                },
-                "urlSuffix": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.StorageType": {
-            "type": "string",
-            "enum": [
-                "S3"
-            ],
-            "x-enum-varnames": [
-                "StorageS3"
-            ]
-        },
-        "github_com_usememos_memos_api_v1.SystemSetting": {
-            "type": "object",
-            "properties": {
-                "description": {
-                    "type": "string"
-                },
-                "name": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.SystemSettingName"
-                },
-                "value": {
-                    "description": "Value is a JSON string with basic value.",
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.SystemSettingName": {
-            "type": "string",
-            "enum": [
-                "server-id",
-                "secret-session",
-                "disable-public-memos",
-                "max-upload-size-mib",
-                "customized-profile",
-                "storage-service-id",
-                "local-storage-path",
-                "telegram-bot-token",
-                "memo-display-with-updated-ts"
-            ],
-            "x-enum-varnames": [
-                "SystemSettingServerIDName",
-                "SystemSettingSecretSessionName",
-                "SystemSettingDisablePublicMemosName",
-                "SystemSettingMaxUploadSizeMiBName",
-                "SystemSettingCustomizedProfileName",
-                "SystemSettingStorageServiceIDName",
-                "SystemSettingLocalStoragePathName",
-                "SystemSettingTelegramBotTokenName",
-                "SystemSettingMemoDisplayWithUpdatedTsName"
-            ]
-        },
-        "github_com_usememos_memos_api_v1.SystemStatus": {
-            "type": "object",
-            "properties": {
-                "additionalScript": {
-                    "description": "Additional script.",
-                    "type": "string"
-                },
-                "additionalStyle": {
-                    "description": "Additional style.",
-                    "type": "string"
-                },
-                "allowSignUp": {
-                    "description": "System settings\nAllow sign up.",
-                    "type": "boolean"
-                },
-                "customizedProfile": {
-                    "description": "Customized server profile, including server name and external url.",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.CustomizedProfile"
-                        }
-                    ]
-                },
-                "dbSize": {
-                    "type": "integer"
-                },
-                "disablePasswordLogin": {
-                    "description": "Disable password login.",
-                    "type": "boolean"
-                },
-                "disablePublicMemos": {
-                    "description": "Disable public memos.",
-                    "type": "boolean"
-                },
-                "host": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.User"
-                },
-                "localStoragePath": {
-                    "description": "Local storage path.",
-                    "type": "string"
-                },
-                "maxUploadSizeMiB": {
-                    "description": "Max upload size.",
-                    "type": "integer"
-                },
-                "memoDisplayWithUpdatedTs": {
-                    "description": "Memo display with updated timestamp.",
-                    "type": "boolean"
-                },
-                "profile": {
-                    "$ref": "#/definitions/profile.Profile"
-                },
-                "storageServiceId": {
-                    "description": "Storage service ID.",
-                    "type": "integer"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpdateIdentityProviderRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProviderConfig"
-                },
-                "identifierFilter": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.IdentityProviderType"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpdateResourceRequest": {
-            "type": "object",
-            "properties": {
-                "filename": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpdateStorageRequest": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.StorageConfig"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.StorageType"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpdateUserRequest": {
-            "type": "object",
-            "properties": {
-                "avatarUrl": {
-                    "type": "string"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "nickname": {
-                    "type": "string"
-                },
-                "password": {
-                    "type": "string"
-                },
-                "rowStatus": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.RowStatus"
-                },
-                "username": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest": {
-            "type": "object",
-            "properties": {
-                "pinned": {
-                    "type": "boolean"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpsertMemoRelationRequest": {
-            "type": "object",
-            "properties": {
-                "relatedMemoId": {
-                    "type": "integer"
-                },
-                "type": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.MemoRelationType"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpsertSystemSettingRequest": {
-            "type": "object",
-            "properties": {
-                "description": {
-                    "type": "string"
-                },
-                "name": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.SystemSettingName"
-                },
-                "value": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.UpsertTagRequest": {
-            "type": "object",
-            "properties": {
-                "name": {
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.User": {
-            "type": "object",
-            "properties": {
-                "avatarUrl": {
-                    "type": "string"
-                },
-                "createdTs": {
-                    "type": "integer"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "nickname": {
-                    "type": "string"
-                },
-                "role": {
-                    "$ref": "#/definitions/github_com_usememos_memos_api_v1.Role"
-                },
-                "rowStatus": {
-                    "description": "Standard fields",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/github_com_usememos_memos_api_v1.RowStatus"
-                        }
-                    ]
-                },
-                "updatedTs": {
-                    "type": "integer"
-                },
-                "username": {
-                    "description": "Domain specific fields",
-                    "type": "string"
-                }
-            }
-        },
-        "github_com_usememos_memos_api_v1.Visibility": {
-            "type": "string",
-            "enum": [
-                "PUBLIC",
-                "PROTECTED",
-                "PRIVATE"
-            ],
-            "x-enum-varnames": [
-                "Public",
-                "Protected",
-                "Private"
-            ]
-        },
-        "profile.Profile": {
-            "type": "object",
-            "properties": {
-                "mode": {
-                    "description": "Mode can be \"prod\" or \"dev\" or \"demo\"",
-                    "type": "string"
-                },
-                "version": {
-                    "description": "Version is the current version of server",
-                    "type": "string"
-                }
-            }
-        },
-        "store.FieldMapping": {
-            "type": "object",
-            "properties": {
-                "displayName": {
-                    "type": "string"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "identifier": {
-                    "type": "string"
-                }
-            }
-        },
-        "store.IdentityProvider": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "$ref": "#/definitions/store.IdentityProviderConfig"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "identifierFilter": {
-                    "type": "string"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "$ref": "#/definitions/store.IdentityProviderType"
-                }
-            }
-        },
-        "store.IdentityProviderConfig": {
-            "type": "object",
-            "properties": {
-                "oauth2Config": {
-                    "$ref": "#/definitions/store.IdentityProviderOAuth2Config"
-                }
-            }
-        },
-        "store.IdentityProviderOAuth2Config": {
-            "type": "object",
-            "properties": {
-                "authUrl": {
-                    "type": "string"
-                },
-                "clientId": {
-                    "type": "string"
-                },
-                "clientSecret": {
-                    "type": "string"
-                },
-                "fieldMapping": {
-                    "$ref": "#/definitions/store.FieldMapping"
-                },
-                "scopes": {
-                    "type": "array",
-                    "items": {
-                        "type": "string"
-                    }
-                },
-                "tokenUrl": {
-                    "type": "string"
-                },
-                "userInfoUrl": {
-                    "type": "string"
-                }
-            }
-        },
-        "store.IdentityProviderType": {
-            "type": "string",
-            "enum": [
-                "OAUTH2"
-            ],
-            "x-enum-varnames": [
-                "IdentityProviderOAuth2Type"
-            ]
-        },
-        "store.Memo": {
-            "type": "object",
-            "properties": {
-                "content": {
-                    "description": "Domain specific fields",
-                    "type": "string"
-                },
-                "createdTs": {
-                    "type": "integer"
-                },
-                "creatorID": {
-                    "type": "integer"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "parentID": {
-                    "type": "integer"
-                },
-                "pinned": {
-                    "description": "Composed fields",
-                    "type": "boolean"
-                },
-                "resourceName": {
-                    "type": "string"
-                },
-                "rowStatus": {
-                    "description": "Standard fields",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/store.RowStatus"
-                        }
-                    ]
-                },
-                "updatedTs": {
-                    "type": "integer"
-                },
-                "visibility": {
-                    "$ref": "#/definitions/store.Visibility"
-                }
-            }
-        },
-        "store.MemoRelation": {
-            "type": "object",
-            "properties": {
-                "memoID": {
-                    "type": "integer"
-                },
-                "relatedMemoID": {
-                    "type": "integer"
-                },
-                "type": {
-                    "$ref": "#/definitions/store.MemoRelationType"
-                }
-            }
-        },
-        "store.MemoRelationType": {
-            "type": "string",
-            "enum": [
-                "REFERENCE",
-                "COMMENT"
-            ],
-            "x-enum-varnames": [
-                "MemoRelationReference",
-                "MemoRelationComment"
-            ]
-        },
-        "store.Resource": {
-            "type": "object",
-            "properties": {
-                "blob": {
-                    "type": "array",
-                    "items": {
-                        "type": "integer"
-                    }
-                },
-                "createdTs": {
-                    "type": "integer"
-                },
-                "creatorID": {
-                    "description": "Standard fields",
-                    "type": "integer"
-                },
-                "externalLink": {
-                    "type": "string"
-                },
-                "filename": {
-                    "description": "Domain specific fields",
-                    "type": "string"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "internalPath": {
-                    "type": "string"
-                },
-                "memoID": {
-                    "type": "integer"
-                },
-                "resourceName": {
-                    "type": "string"
-                },
-                "size": {
-                    "type": "integer"
-                },
-                "type": {
-                    "type": "string"
-                },
-                "updatedTs": {
-                    "type": "integer"
-                }
-            }
-        },
-        "store.Role": {
-            "type": "string",
-            "enum": [
-                "HOST",
-                "ADMIN",
-                "USER"
-            ],
-            "x-enum-varnames": [
-                "RoleHost",
-                "RoleAdmin",
-                "RoleUser"
-            ]
-        },
-        "store.RowStatus": {
-            "type": "string",
-            "enum": [
-                "NORMAL",
-                "ARCHIVED"
-            ],
-            "x-enum-varnames": [
-                "Normal",
-                "Archived"
-            ]
-        },
-        "store.Storage": {
-            "type": "object",
-            "properties": {
-                "config": {
-                    "type": "string"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "name": {
-                    "type": "string"
-                },
-                "type": {
-                    "type": "string"
-                }
-            }
-        },
-        "store.User": {
-            "type": "object",
-            "properties": {
-                "avatarURL": {
-                    "type": "string"
-                },
-                "createdTs": {
-                    "type": "integer"
-                },
-                "email": {
-                    "type": "string"
-                },
-                "id": {
-                    "type": "integer"
-                },
-                "nickname": {
-                    "type": "string"
-                },
-                "passwordHash": {
-                    "type": "string"
-                },
-                "role": {
-                    "$ref": "#/definitions/store.Role"
-                },
-                "rowStatus": {
-                    "description": "Standard fields",
-                    "allOf": [
-                        {
-                            "$ref": "#/definitions/store.RowStatus"
-                        }
-                    ]
-                },
-                "updatedTs": {
-                    "type": "integer"
-                },
-                "username": {
-                    "description": "Domain specific fields",
-                    "type": "string"
-                }
-            }
-        },
-        "store.Visibility": {
-            "type": "string",
-            "enum": [
-                "PUBLIC",
-                "PROTECTED",
-                "PRIVATE"
-            ],
-            "x-enum-varnames": [
-                "Public",
-                "Protected",
-                "Private"
-            ]
-        }
-    },
-    "externalDocs": {
-        "description": "Find out more about Memos.",
-        "url": "https://usememos.com/"
-    }
-}`
-
-// SwaggerInfo holds exported Swagger Info so clients can modify it
-var SwaggerInfo = &swag.Spec{
-	Version:          "1.0",
-	Host:             "",
-	BasePath:         "/",
-	Schemes:          []string{},
-	Title:            "memos API",
-	Description:      "A privacy-first, lightweight note-taking service.",
-	InfoInstanceName: "swagger",
-	SwaggerTemplate:  docTemplate,
-	LeftDelim:        "{{",
-	RightDelim:       "}}",
-}
-
-func init() {
-	swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
-}

programming_language: unknown
filename: http_getter.go
change_type: ModificationType.DELETE
diff: @@ -1,49 +0,0 @@
-package v1
-
-import (
-	"fmt"
-	"net/http"
-	"net/url"
-
-	"github.com/labstack/echo/v4"
-
-	getter "github.com/usememos/memos/plugin/http-getter"
-)
-
-func (*APIV1Service) registerGetterPublicRoutes(g *echo.Group) {
-	// GET /get/image?url={url} - Get image.
-	g.GET("/get/image", GetImage)
-}
-
-// GetImage godoc
-//
-//	@Summary	Get GetImage from URL
-//	@Tags		image-url
-//	@Produce	GetImage/*
-//	@Param		url	query		string	true	"Image url"
-//	@Success	200	{object}	nil		"Image"
-//	@Failure	400	{object}	nil		"Missing GetImage url | Wrong url | Failed to get GetImage url: %s"
-//	@Failure	500	{object}	nil		"Failed to write GetImage blob"
-//	@Router		/o/get/GetImage [GET]
-func GetImage(c echo.Context) error {
-	urlStr := c.QueryParam("url")
-	if urlStr == "" {
-		return echo.NewHTTPError(http.StatusBadRequest, "Missing image url")
-	}
-	if _, err := url.Parse(urlStr); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Wrong url").SetInternal(err)
-	}
-
-	image, err := getter.GetImage(urlStr)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to get image url: %s", urlStr)).SetInternal(err)
-	}
-
-	c.Response().Writer.WriteHeader(http.StatusOK)
-	c.Response().Writer.Header().Set("Content-Type", image.Mediatype)
-	c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable")
-	if _, err := c.Response().Writer.Write(image.Blob); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to write image blob").SetInternal(err)
-	}
-	return nil
-}

programming_language: unknown
filename: jwt.go
change_type: ModificationType.DELETE
diff: @@ -1,155 +0,0 @@
-package v1
-
-import (
-	"fmt"
-	"log/slog"
-	"net/http"
-	"strings"
-
-	"github.com/golang-jwt/jwt/v5"
-	"github.com/labstack/echo/v4"
-	"github.com/pkg/errors"
-
-	"github.com/usememos/memos/internal/util"
-	storepb "github.com/usememos/memos/proto/gen/store"
-	"github.com/usememos/memos/server/route/api/auth"
-	"github.com/usememos/memos/store"
-)
-
-const (
-	// The key name used to store user id in the context
-	// user id is extracted from the jwt token subject field.
-	userIDContextKey = "user-id"
-)
-
-func extractTokenFromHeader(c echo.Context) (string, error) {
-	authHeader := c.Request().Header.Get("Authorization")
-	if authHeader == "" {
-		return "", nil
-	}
-
-	authHeaderParts := strings.Fields(authHeader)
-	if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
-		return "", errors.New("Authorization header format must be Bearer {token}")
-	}
-
-	return authHeaderParts[1], nil
-}
-
-func findAccessToken(c echo.Context) string {
-	// Check the HTTP request header first.
-	accessToken, _ := extractTokenFromHeader(c)
-	if accessToken == "" {
-		// Check the cookie.
-		cookie, _ := c.Cookie(auth.AccessTokenCookieName)
-		if cookie != nil {
-			accessToken = cookie.Value
-		}
-	}
-	return accessToken
-}
-
-// JWTMiddleware validates the access token.
-func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) echo.HandlerFunc {
-	return func(c echo.Context) error {
-		ctx := c.Request().Context()
-		path := c.Request().URL.Path
-		method := c.Request().Method
-
-		if server.defaultAuthSkipper(c) {
-			return next(c)
-		}
-
-		// Skip validation for server status endpoints.
-		if util.HasPrefixes(path, "/api/v1/ping", "/api/v1/status") && method == http.MethodGet {
-			return next(c)
-		}
-
-		accessToken := findAccessToken(c)
-		if accessToken == "" {
-			// Allow the user to access the public endpoints.
-			if util.HasPrefixes(path, "/o") {
-				return next(c)
-			}
-			// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
-			if util.HasPrefixes(path, "/api/v1/idp", "/api/v1/memo", "/api/v1/user") && path != "/api/v1/user" && method == http.MethodGet {
-				return next(c)
-			}
-			return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
-		}
-
-		userID, err := getUserIDFromAccessToken(accessToken, secret)
-		if err != nil {
-			err = removeAccessTokenAndCookies(c, server.Store, userID, accessToken)
-			if err != nil {
-				slog.Warn("fail to remove AccessToken and Cookies", err)
-			}
-			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired access token")
-		}
-
-		accessTokens, err := server.Store.GetUserAccessTokens(ctx, userID)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get user access tokens.").WithInternal(err)
-		}
-		if !validateAccessToken(accessToken, accessTokens) {
-			err = removeAccessTokenAndCookies(c, server.Store, userID, accessToken)
-			if err != nil {
-				slog.Warn("fail to remove AccessToken and Cookies", err)
-			}
-			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
-		}
-
-		// Even if there is no error, we still need to make sure the user still exists.
-		user, err := server.Store.GetUser(ctx, &store.FindUser{
-			ID: &userID,
-		})
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
-		}
-		if user == nil {
-			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
-		}
-
-		// Stores userID into context.
-		c.Set(userIDContextKey, userID)
-		return next(c)
-	}
-}
-
-func getUserIDFromAccessToken(accessToken, secret string) (int32, error) {
-	claims := &auth.ClaimsMessage{}
-	_, err := jwt.ParseWithClaims(accessToken, claims, func(t *jwt.Token) (any, error) {
-		if t.Method.Alg() != jwt.SigningMethodHS256.Name {
-			return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
-		}
-		if kid, ok := t.Header["kid"].(string); ok {
-			if kid == "v1" {
-				return []byte(secret), nil
-			}
-		}
-		return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
-	})
-	if err != nil {
-		return 0, errors.Wrap(err, "Invalid or expired access token")
-	}
-	// We either have a valid access token or we will attempt to generate new access token.
-	userID, err := util.ConvertStringToInt32(claims.Subject)
-	if err != nil {
-		return 0, errors.Wrap(err, "Malformed ID in the token")
-	}
-	return userID, nil
-}
-
-func (*APIV1Service) defaultAuthSkipper(c echo.Context) bool {
-	path := c.Path()
-	return util.HasPrefixes(path, "/api/v1/auth")
-}
-
-func validateAccessToken(accessTokenString string, userAccessTokens []*storepb.AccessTokensUserSetting_AccessToken) bool {
-	for _, userAccessToken := range userAccessTokens {
-		if accessTokenString == userAccessToken.AccessToken {
-			return true
-		}
-	}
-	return false
-}

programming_language: unknown
filename: memo.go
change_type: ModificationType.DELETE
diff: @@ -1,943 +0,0 @@
-package v1
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"log/slog"
-	"net/http"
-	"strconv"
-	"time"
-
-	"github.com/labstack/echo/v4"
-	"github.com/lithammer/shortuuid/v4"
-	"github.com/pkg/errors"
-
-	"github.com/usememos/memos/internal/util"
-	"github.com/usememos/memos/plugin/webhook"
-	storepb "github.com/usememos/memos/proto/gen/store"
-	"github.com/usememos/memos/store"
-)
-
-// 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 Memo struct {
-	ID   int32  `json:"id"`
-	Name string `json:"name"`
-
-	// Standard fields
-	RowStatus RowStatus `json:"rowStatus"`
-	CreatorID int32     `json:"creatorId"`
-	CreatedTs int64     `json:"createdTs"`
-	UpdatedTs int64     `json:"updatedTs"`
-
-	// Domain specific fields
-	DisplayTs  int64      `json:"displayTs"`
-	Content    string     `json:"content"`
-	Visibility Visibility `json:"visibility"`
-	Pinned     bool       `json:"pinned"`
-
-	// Related fields
-	CreatorName     string          `json:"creatorName"`
-	CreatorUsername string          `json:"creatorUsername"`
-	ResourceList    []*Resource     `json:"resourceList"`
-	RelationList    []*MemoRelation `json:"relationList"`
-}
-
-type CreateMemoRequest struct {
-	// Standard fields
-	CreatorID int32  `json:"-"`
-	CreatedTs *int64 `json:"createdTs"`
-
-	// Domain specific fields
-	Visibility Visibility `json:"visibility"`
-	Content    string     `json:"content"`
-
-	// Related fields
-	ResourceIDList []int32                      `json:"resourceIdList"`
-	RelationList   []*UpsertMemoRelationRequest `json:"relationList"`
-}
-
-type PatchMemoRequest struct {
-	ID int32 `json:"-"`
-
-	// Standard fields
-	CreatedTs *int64 `json:"createdTs"`
-	UpdatedTs *int64
-	RowStatus *RowStatus `json:"rowStatus"`
-
-	// Domain specific fields
-	Content    *string     `json:"content"`
-	Visibility *Visibility `json:"visibility"`
-
-	// Related fields
-	ResourceIDList []int32                      `json:"resourceIdList"`
-	RelationList   []*UpsertMemoRelationRequest `json:"relationList"`
-}
-
-type FindMemoRequest struct {
-	ID *int32
-
-	// Standard fields
-	RowStatus *RowStatus
-	CreatorID *int32
-
-	// Domain specific fields
-	Pinned         *bool
-	ContentSearch  []string
-	VisibilityList []Visibility
-
-	// Pagination
-	Limit  *int
-	Offset *int
-}
-
-// maxContentLength means the max memo content bytes is 1MB.
-const maxContentLength = 1 << 30
-
-func (s *APIV1Service) registerMemoRoutes(g *echo.Group) {
-	g.GET("/memo", s.GetMemoList)
-	g.POST("/memo", s.CreateMemo)
-	g.GET("/memo/all", s.GetAllMemos)
-	g.GET("/memo/stats", s.GetMemoStats)
-	g.GET("/memo/:memoId", s.GetMemo)
-	g.PATCH("/memo/:memoId", s.UpdateMemo)
-	g.DELETE("/memo/:memoId", s.DeleteMemo)
-}
-
-// GetMemoList godoc
-//
-//	@Summary	Get a list of memos matching optional filters
-//	@Tags		memo
-//	@Produce	json
-//	@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}	[]store.Memo	"Memo list"
-//	@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 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
-		}
-	}
-
-	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)
-		}
-		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
-	}
-
-	list, err := s.Store.ListMemos(ctx, find)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err)
-	}
-	memoResponseList := []*Memo{}
-	for _, memo := range list {
-		memoResponse, err := s.convertMemoFromStore(ctx, memo)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
-		}
-		memoResponseList = append(memoResponseList, memoResponse)
-	}
-	return c.JSON(http.StatusOK, memoResponseList)
-}
-
-// CreateMemo godoc
-//
-//	@Summary		Create a memo
-//	@Description	Visibility can be PUBLIC, PROTECTED or PRIVATE
-//	@Description	*You should omit fields to use their default values
-//	@Tags			memo
-//	@Accept			json
-//	@Produce		json
-//	@Param			body	body		CreateMemoRequest	true	"Request object."
-//	@Success		200		{object}	store.Memo			"Stored memo"
-//	@Failure		400		{object}	nil					"Malformatted post memo request | Content size overflow, up to 1MB"
-//	@Failure		401		{object}	nil					"Missing user in session"
-//	@Failure		404		{object}	nil					"User not found | Memo not found: %d"
-//	@Failure		500		{object}	nil					"Failed to find user setting | Failed to unmarshal user setting value | Failed to find system setting | Failed to unmarshal system setting | Failed to find user | Failed to create memo | Failed to create activity | Failed to upsert memo resource | Failed to upsert memo relation | Failed to compose memo | Failed to compose memo response"
-//	@Router			/api/v1/memo [POST]
-//
-// NOTES:
-// - It's currently possible to create phantom resources and relations. Phantom relations will trigger backend 404's when fetching memo.
-func (s *APIV1Service) CreateMemo(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	createMemoRequest := &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 createMemoRequest.Visibility == "" {
-		userMemoVisibilitySetting, err := s.Store.GetUserSetting(ctx, &store.FindUserSetting{
-			UserID: &userID,
-			Key:    storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY,
-		})
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user setting").SetInternal(err)
-		}
-		if userMemoVisibilitySetting != nil {
-			createMemoRequest.Visibility = Visibility(userMemoVisibilitySetting.GetMemoVisibility())
-		} else {
-			// Private is the default memo visibility.
-			createMemoRequest.Visibility = Private
-		}
-	}
-
-	createMemoRequest.CreatorID = userID
-	memo, err := s.Store.CreateMemo(ctx, convertCreateMemoRequestToMemoMessage(createMemoRequest))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create memo").SetInternal(err)
-	}
-
-	for _, resourceID := range createMemoRequest.ResourceIDList {
-		if _, err := s.Store.UpdateResource(ctx, &store.UpdateResource{
-			ID:     resourceID,
-			MemoID: &memo.ID,
-		}); err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo resource").SetInternal(err)
-		}
-	}
-
-	for _, memoRelationUpsert := range createMemoRequest.RelationList {
-		if _, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
-			MemoID:        memo.ID,
-			RelatedMemoID: memoRelationUpsert.RelatedMemoID,
-			Type:          store.MemoRelationType(memoRelationUpsert.Type),
-		}); err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
-		}
-		if memo.Visibility != store.Private && memoRelationUpsert.Type == MemoRelationComment {
-			relatedMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{
-				ID: &memoRelationUpsert.RelatedMemoID,
-			})
-			if err != nil {
-				return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get related memo").SetInternal(err)
-			}
-			if relatedMemo.CreatorID != memo.CreatorID {
-				activity, err := s.Store.CreateActivity(ctx, &store.Activity{
-					CreatorID: memo.CreatorID,
-					Type:      store.ActivityTypeMemoComment,
-					Level:     store.ActivityLevelInfo,
-					Payload: &storepb.ActivityPayload{
-						MemoComment: &storepb.ActivityMemoCommentPayload{
-							MemoId:        memo.ID,
-							RelatedMemoId: memoRelationUpsert.RelatedMemoID,
-						},
-					},
-				})
-				if err != nil {
-					return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
-				}
-				if _, err := s.Store.CreateInbox(ctx, &store.Inbox{
-					SenderID:   memo.CreatorID,
-					ReceiverID: relatedMemo.CreatorID,
-					Status:     store.UNREAD,
-					Message: &storepb.InboxMessage{
-						Type:       storepb.InboxMessage_TYPE_MEMO_COMMENT,
-						ActivityId: &activity.ID,
-					},
-				}); err != nil {
-					return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create inbox").SetInternal(err)
-				}
-			}
-		}
-	}
-
-	composedMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{
-		ID: &memo.ID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo").SetInternal(err)
-	}
-	if composedMemo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memo.ID))
-	}
-
-	memoResponse, err := s.convertMemoFromStore(ctx, composedMemo)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
-	}
-
-	// Send notification to telegram if memo is not private.
-	if memoResponse.Visibility != Private {
-		// fetch all telegram UserID
-		userSettings, err := s.Store.ListUserSettings(ctx, &store.FindUserSetting{Key: storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID})
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to ListUserSettings").SetInternal(err)
-		}
-		for _, userSetting := range userSettings {
-			tgUserID, err := strconv.ParseInt(userSetting.GetTelegramUserId(), 10, 64)
-			if err != nil {
-				continue
-			}
-
-			// send notification to telegram
-			content := memoResponse.CreatorName + " Says:\n\n" + memoResponse.Content
-			_, err = s.telegramBot.SendMessage(ctx, tgUserID, content)
-			if err != nil {
-				continue
-			}
-		}
-	}
-	// Try to dispatch webhook when memo is created.
-	if err := s.DispatchMemoCreatedWebhook(ctx, memoResponse); err != nil {
-		slog.Warn("Failed to dispatch memo created webhook", err)
-	}
-
-	return c.JSON(http.StatusOK, memoResponse)
-}
-
-// GetAllMemos godoc
-//
-//	@Summary		Get a list of public memos matching optional filters
-//	@Description	This should also list protected memos if the user is logged in
-//	@Description	Authentication is optional
-//	@Tags			memo
-//	@Produce		json
-//	@Param			limit	query		int				false	"Limit"
-//	@Param			offset	query		int				false	"Offset"
-//	@Success		200		{object}	[]store.Memo	"Memo list"
-//	@Failure		500		{object}	nil				"Failed to get memo display with updated ts setting value | Failed to fetch all memo list | Failed to compose memo response"
-//	@Router			/api/v1/memo/all [GET]
-//
-//	NOTES:
-//	- creatorUsername is listed at ./web/src/helpers/api.ts:82, but it's not present here
-func (s *APIV1Service) GetAllMemos(c echo.Context) error {
-	ctx := c.Request().Context()
-	memoFind := &store.FindMemo{}
-	_, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		memoFind.VisibilityList = []store.Visibility{store.Public}
-	} else {
-		memoFind.VisibilityList = []store.Visibility{store.Public, store.Protected}
-	}
-
-	if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
-		memoFind.Limit = &limit
-	}
-	if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
-		memoFind.Offset = &offset
-	}
-
-	// Only fetch normal status memos.
-	normalStatus := store.Normal
-	memoFind.RowStatus = &normalStatus
-
-	list, err := s.Store.ListMemos(ctx, memoFind)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch all memo list").SetInternal(err)
-	}
-	memoResponseList := []*Memo{}
-	for _, memo := range list {
-		memoResponse, err := s.convertMemoFromStore(ctx, memo)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
-		}
-		memoResponseList = append(memoResponseList, memoResponse)
-	}
-	return c.JSON(http.StatusOK, memoResponseList)
-}
-
-// GetMemoStats godoc
-//
-//	@Summary		Get memo stats by creator ID or username
-//	@Description	Used to generate the heatmap
-//	@Tags			memo
-//	@Produce		json
-//	@Param			creatorId		query		int		false	"Creator ID"
-//	@Param			creatorUsername	query		string	false	"Creator username"
-//	@Success		200				{object}	[]int	"Memo createdTs list"
-//	@Failure		400				{object}	nil		"Missing user id to find memo"
-//	@Failure		500				{object}	nil		"Failed to get memo display with updated ts setting value | Failed to find memo list | Failed to compose memo response"
-//	@Router			/api/v1/memo/stats [GET]
-func (s *APIV1Service) GetMemoStats(c echo.Context) error {
-	ctx := c.Request().Context()
-	normalStatus := store.Normal
-	findMemoMessage := &store.FindMemo{
-		RowStatus:      &normalStatus,
-		ExcludeContent: true,
-	}
-	if creatorID, err := util.ConvertStringToInt32(c.QueryParam("creatorId")); err == nil {
-		findMemoMessage.CreatorID = &creatorID
-	}
-
-	if username := c.QueryParam("creatorUsername"); username != "" {
-		user, _ := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
-		if user != nil {
-			findMemoMessage.CreatorID = &user.ID
-		}
-	}
-
-	if findMemoMessage.CreatorID == nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find memo")
-	}
-
-	currentUserID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		findMemoMessage.VisibilityList = []store.Visibility{store.Public}
-	} else {
-		if *findMemoMessage.CreatorID != currentUserID {
-			findMemoMessage.VisibilityList = []store.Visibility{store.Public, store.Protected}
-		} else {
-			findMemoMessage.VisibilityList = []store.Visibility{store.Public, store.Protected, store.Private}
-		}
-	}
-
-	list, err := s.Store.ListMemos(ctx, findMemoMessage)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
-	}
-
-	displayTsList := []int64{}
-	for _, memo := range list {
-		displayTsList = append(displayTsList, memo.CreatedTs)
-	}
-	return c.JSON(http.StatusOK, displayTsList)
-}
-
-// GetMemo godoc
-//
-//	@Summary	Get memo by ID
-//	@Tags		memo
-//	@Produce	json
-//	@Param		memoId	path		int				true	"Memo ID"
-//	@Success	200		{object}	[]store.Memo	"Memo list"
-//	@Failure	400		{object}	nil				"ID is not a number: %s"
-//	@Failure	401		{object}	nil				"Missing user in session"
-//	@Failure	403		{object}	nil				"this memo is private only | this memo is protected, missing user in session
-//	@Failure	404		{object}	nil				"Memo not found: %d"
-//	@Failure	500		{object}	nil				"Failed to find memo by ID: %v | Failed to compose memo response"
-//	@Router		/api/v1/memo/{memoId} [GET]
-func (s *APIV1Service) GetMemo(c echo.Context) error {
-	ctx := c.Request().Context()
-	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
-	}
-
-	memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
-		ID: &memoID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find memo by ID: %v", memoID)).SetInternal(err)
-	}
-	if memo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memoID))
-	}
-
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if memo.Visibility == store.Private {
-		if !ok || memo.CreatorID != userID {
-			return echo.NewHTTPError(http.StatusForbidden, "this memo is private only")
-		}
-	} else if memo.Visibility == store.Protected {
-		if !ok {
-			return echo.NewHTTPError(http.StatusForbidden, "this memo is protected, missing user in session")
-		}
-	}
-	memoResponse, err := s.convertMemoFromStore(ctx, memo)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, memoResponse)
-}
-
-// DeleteMemo godoc
-//
-//	@Summary	Delete memo by ID
-//	@Tags		memo
-//	@Produce	json
-//	@Param		memoId	path		int		true	"Memo ID to delete"
-//	@Success	200		{boolean}	true	"Memo deleted"
-//	@Failure	400		{object}	nil		"ID is not a number: %s"
-//	@Failure	401		{object}	nil		"Missing user in session | Unauthorized"
-//	@Failure	404		{object}	nil		"Memo not found: %d"
-//	@Failure	500		{object}	nil		"Failed to find memo | Failed to delete memo ID: %v"
-//	@Router		/api/v1/memo/{memoId} [DELETE]
-func (s *APIV1Service) DeleteMemo(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
-	}
-
-	memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
-		ID: &memoID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
-	}
-	if memo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memoID))
-	}
-	if memo.CreatorID != userID {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	if memoMessage, err := s.convertMemoFromStore(ctx, memo); err == nil {
-		// Try to dispatch webhook when memo is deleted.
-		if err := s.DispatchMemoDeletedWebhook(ctx, memoMessage); err != nil {
-			slog.Warn("Failed to dispatch memo deleted webhook", err)
-		}
-	}
-
-	if err := s.Store.DeleteMemo(ctx, &store.DeleteMemo{
-		ID: memoID,
-	}); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete memo ID: %v", memoID)).SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, true)
-}
-
-// UpdateMemo godoc
-//
-//	@Summary		Update a memo
-//	@Description	Visibility can be PUBLIC, PROTECTED or PRIVATE
-//	@Description	*You should omit fields to use their default values
-//	@Tags			memo
-//	@Accept			json
-//	@Produce		json
-//	@Param			memoId	path		int					true	"ID of memo to update"
-//	@Param			body	body		PatchMemoRequest	true	"Patched object."
-//	@Success		200		{object}	store.Memo			"Stored memo"
-//	@Failure		400		{object}	nil					"ID is not a number: %s | Malformatted patch memo request | Content size overflow, up to 1MB"
-//	@Failure		401		{object}	nil					"Missing user in session | Unauthorized"
-//	@Failure		404		{object}	nil					"Memo not found: %d"
-//	@Failure		500		{object}	nil					"Failed to find memo | Failed to patch memo | Failed to upsert memo resource | Failed to delete memo resource | Failed to compose memo response"
-//	@Router			/api/v1/memo/{memoId} [PATCH]
-//
-// NOTES:
-// - It's currently possible to create phantom resources and relations. Phantom relations will trigger backend 404's when fetching memo.
-// - Passing 0 to createdTs and updatedTs will set them to 0 in the database, which is probably unwanted.
-func (s *APIV1Service) UpdateMemo(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
-	}
-
-	memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
-		ID: &memoID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
-	}
-	if memo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memoID))
-	}
-	if memo.CreatorID != userID {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	currentTs := time.Now().Unix()
-	patchMemoRequest := &PatchMemoRequest{
-		ID:        memoID,
-		UpdatedTs: &currentTs,
-	}
-	if err := json.NewDecoder(c.Request().Body).Decode(patchMemoRequest); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch memo request").SetInternal(err)
-	}
-
-	if patchMemoRequest.Content != nil && len(*patchMemoRequest.Content) > maxContentLength {
-		return echo.NewHTTPError(http.StatusBadRequest, "Content size overflow, up to 1MB").SetInternal(err)
-	}
-
-	updateMemoMessage := &store.UpdateMemo{
-		ID:        memoID,
-		CreatedTs: patchMemoRequest.CreatedTs,
-		UpdatedTs: patchMemoRequest.UpdatedTs,
-		Content:   patchMemoRequest.Content,
-	}
-	if patchMemoRequest.RowStatus != nil {
-		rowStatus := store.RowStatus(patchMemoRequest.RowStatus.String())
-		updateMemoMessage.RowStatus = &rowStatus
-	}
-
-	err = s.Store.UpdateMemo(ctx, updateMemoMessage)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch memo").SetInternal(err)
-	}
-	memo, err = s.Store.GetMemo(ctx, &store.FindMemo{ID: &memoID})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
-	}
-	if memo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memoID))
-	}
-
-	memoMessage, err := s.convertMemoFromStore(ctx, memo)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo").SetInternal(err)
-	}
-	if patchMemoRequest.ResourceIDList != nil {
-		originResourceIDList := []int32{}
-		for _, resource := range memoMessage.ResourceList {
-			originResourceIDList = append(originResourceIDList, resource.ID)
-		}
-		addedResourceIDList, removedResourceIDList := getIDListDiff(originResourceIDList, patchMemoRequest.ResourceIDList)
-		for _, resourceID := range addedResourceIDList {
-			if _, err := s.Store.UpdateResource(ctx, &store.UpdateResource{
-				ID:     resourceID,
-				MemoID: &memo.ID,
-			}); err != nil {
-				return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo resource").SetInternal(err)
-			}
-		}
-		for _, resourceID := range removedResourceIDList {
-			if err := s.Store.DeleteResource(ctx, &store.DeleteResource{
-				ID: resourceID,
-			}); err != nil {
-				return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err)
-			}
-		}
-	}
-
-	if patchMemoRequest.RelationList != nil {
-		patchMemoRelationList := make([]*MemoRelation, 0)
-		for _, memoRelation := range patchMemoRequest.RelationList {
-			patchMemoRelationList = append(patchMemoRelationList, &MemoRelation{
-				MemoID:        memo.ID,
-				RelatedMemoID: memoRelation.RelatedMemoID,
-				Type:          memoRelation.Type,
-			})
-		}
-		addedMemoRelationList, removedMemoRelationList := getMemoRelationListDiff(memoMessage.RelationList, patchMemoRelationList)
-		for _, memoRelation := range addedMemoRelationList {
-			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 {
-			if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
-				MemoID:        &memo.ID,
-				RelatedMemoID: &memoRelation.RelatedMemoID,
-				Type:          &memoRelation.Type,
-			}); err != nil {
-				return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete memo relation").SetInternal(err)
-			}
-		}
-	}
-
-	memo, err = s.Store.GetMemo(ctx, &store.FindMemo{ID: &memoID})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
-	}
-	if memo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %d", memoID))
-	}
-
-	memoResponse, err := s.convertMemoFromStore(ctx, memo)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
-	}
-	// Try to dispatch webhook when memo is updated.
-	if err := s.DispatchMemoUpdatedWebhook(ctx, memoResponse); err != nil {
-		slog.Error("Failed to dispatch memo updated webhook", err)
-	}
-
-	return c.JSON(http.StatusOK, memoResponse)
-}
-
-func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Memo) (*Memo, error) {
-	memoMessage := &Memo{
-		ID:         memo.ID,
-		Name:       memo.UID,
-		RowStatus:  RowStatus(memo.RowStatus.String()),
-		CreatorID:  memo.CreatorID,
-		CreatedTs:  memo.CreatedTs,
-		UpdatedTs:  memo.UpdatedTs,
-		Content:    memo.Content,
-		Visibility: Visibility(memo.Visibility.String()),
-		Pinned:     memo.Pinned,
-	}
-
-	// Compose creator name.
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &memoMessage.CreatorID,
-	})
-	if err != nil {
-		return nil, err
-	}
-	if user.Nickname != "" {
-		memoMessage.CreatorName = user.Nickname
-	} else {
-		memoMessage.CreatorName = user.Username
-	}
-	memoMessage.CreatorUsername = user.Username
-
-	// Compose display ts.
-	memoMessage.DisplayTs = memoMessage.CreatedTs
-
-	// Compose related resources.
-	resourceList, err := s.Store.ListResources(ctx, &store.FindResource{
-		MemoID: &memo.ID,
-	})
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to list resources")
-	}
-	memoMessage.ResourceList = []*Resource{}
-	for _, resource := range resourceList {
-		memoMessage.ResourceList = append(memoMessage.ResourceList, convertResourceFromStore(resource))
-	}
-
-	// Compose related memo relations.
-	relationList := []*MemoRelation{}
-	tempList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
-		MemoID: &memo.ID,
-	})
-	if err != nil {
-		return nil, err
-	}
-	for _, relation := range tempList {
-		relationList = append(relationList, convertMemoRelationFromStore(relation))
-	}
-	tempList, err = s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
-		RelatedMemoID: &memo.ID,
-	})
-	if err != nil {
-		return nil, err
-	}
-	for _, relation := range tempList {
-		relationList = append(relationList, convertMemoRelationFromStore(relation))
-	}
-	memoMessage.RelationList = relationList
-	return memoMessage, nil
-}
-
-func convertCreateMemoRequestToMemoMessage(memoCreate *CreateMemoRequest) *store.Memo {
-	createdTs := time.Now().Unix()
-	if memoCreate.CreatedTs != nil {
-		createdTs = *memoCreate.CreatedTs
-	}
-	return &store.Memo{
-		UID:        shortuuid.New(),
-		CreatorID:  memoCreate.CreatorID,
-		CreatedTs:  createdTs,
-		Content:    memoCreate.Content,
-		Visibility: store.Visibility(memoCreate.Visibility),
-	}
-}
-
-func getMemoRelationListDiff(oldList, newList []*MemoRelation) (addedList, removedList []*store.MemoRelation) {
-	oldMap := map[string]bool{}
-	for _, relation := range oldList {
-		oldMap[fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)] = true
-	}
-	newMap := map[string]bool{}
-	for _, relation := range newList {
-		newMap[fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)] = true
-	}
-	for _, relation := range oldList {
-		key := fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)
-		if !newMap[key] {
-			removedList = append(removedList, &store.MemoRelation{
-				MemoID:        relation.MemoID,
-				RelatedMemoID: relation.RelatedMemoID,
-				Type:          store.MemoRelationType(relation.Type),
-			})
-		}
-	}
-	for _, relation := range newList {
-		key := fmt.Sprintf("%d-%s", relation.RelatedMemoID, relation.Type)
-		if !oldMap[key] {
-			addedList = append(addedList, &store.MemoRelation{
-				MemoID:        relation.MemoID,
-				RelatedMemoID: relation.RelatedMemoID,
-				Type:          store.MemoRelationType(relation.Type),
-			})
-		}
-	}
-	return addedList, removedList
-}
-
-func getIDListDiff(oldList, newList []int32) (addedList, removedList []int32) {
-	oldMap := map[int32]bool{}
-	for _, id := range oldList {
-		oldMap[id] = true
-	}
-	newMap := map[int32]bool{}
-	for _, id := range newList {
-		newMap[id] = true
-	}
-	for id := range oldMap {
-		if !newMap[id] {
-			removedList = append(removedList, id)
-		}
-	}
-	for id := range newMap {
-		if !oldMap[id] {
-			addedList = append(addedList, id)
-		}
-	}
-	return addedList, removedList
-}
-
-// 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")
-}
-
-// DispatchMemoUpdatedWebhook dispatches webhook when memo is updated.
-func (s *APIV1Service) DispatchMemoUpdatedWebhook(ctx context.Context, memo *Memo) error {
-	return s.dispatchMemoRelatedWebhook(ctx, memo, "memos.memo.updated")
-}
-
-// DispatchMemoDeletedWebhook dispatches webhook when memo is deletedd.
-func (s *APIV1Service) DispatchMemoDeletedWebhook(ctx context.Context, memo *Memo) error {
-	return s.dispatchMemoRelatedWebhook(ctx, memo, "memos.memo.deleted")
-}
-
-func (s *APIV1Service) dispatchMemoRelatedWebhook(ctx context.Context, memo *Memo, activityType string) error {
-	webhooks, err := s.Store.ListWebhooks(ctx, &store.FindWebhook{
-		CreatorID: &memo.CreatorID,
-	})
-	if err != nil {
-		return err
-	}
-	for _, hook := range webhooks {
-		payload := convertMemoToWebhookPayload(memo)
-		payload.ActivityType = activityType
-		payload.URL = hook.Url
-		err := webhook.Post(*payload)
-		if err != nil {
-			return errors.Wrap(err, "failed to post webhook")
-		}
-	}
-	return nil
-}
-
-func convertMemoToWebhookPayload(memo *Memo) *webhook.WebhookPayload {
-	return &webhook.WebhookPayload{
-		CreatorID: memo.CreatorID,
-		CreatedTs: time.Now().Unix(),
-		Memo: &webhook.Memo{
-			ID:         memo.ID,
-			CreatorID:  memo.CreatorID,
-			CreatedTs:  memo.CreatedTs,
-			UpdatedTs:  memo.UpdatedTs,
-			Content:    memo.Content,
-			Visibility: memo.Visibility.String(),
-			Pinned:     memo.Pinned,
-			ResourceList: func() []*webhook.Resource {
-				resources := []*webhook.Resource{}
-				for _, resource := range memo.ResourceList {
-					resources = append(resources, &webhook.Resource{
-						ID:           resource.ID,
-						CreatorID:    resource.CreatorID,
-						CreatedTs:    resource.CreatedTs,
-						UpdatedTs:    resource.UpdatedTs,
-						Filename:     resource.Filename,
-						InternalPath: resource.InternalPath,
-						ExternalLink: resource.ExternalLink,
-						Type:         resource.Type,
-						Size:         resource.Size,
-					})
-				}
-				return resources
-			}(),
-			RelationList: func() []*webhook.MemoRelation {
-				relations := []*webhook.MemoRelation{}
-				for _, relation := range memo.RelationList {
-					relations = append(relations, &webhook.MemoRelation{
-						MemoID:        relation.MemoID,
-						RelatedMemoID: relation.RelatedMemoID,
-						Type:          relation.Type.String(),
-					})
-				}
-				return relations
-			}(),
-		},
-	}
-}

programming_language: unknown
filename: memo_organizer.go
change_type: ModificationType.DELETE
diff: @@ -1,97 +0,0 @@
-package v1
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-
-	"github.com/labstack/echo/v4"
-
-	"github.com/usememos/memos/internal/util"
-	"github.com/usememos/memos/store"
-)
-
-type MemoOrganizer struct {
-	MemoID int32 `json:"memoId"`
-	UserID int32 `json:"userId"`
-	Pinned bool  `json:"pinned"`
-}
-
-type UpsertMemoOrganizerRequest struct {
-	Pinned bool `json:"pinned"`
-}
-
-func (s *APIV1Service) registerMemoOrganizerRoutes(g *echo.Group) {
-	g.POST("/memo/:memoId/organizer", s.CreateMemoOrganizer)
-}
-
-// CreateMemoOrganizer godoc
-//
-//	@Summary	Organize memo (pin/unpin)
-//	@Tags		memo-organizer
-//	@Accept		json
-//	@Produce	json
-//	@Param		memoId	path		int							true	"ID of memo to organize"
-//	@Param		body	body		UpsertMemoOrganizerRequest	true	"Memo organizer object"
-//	@Success	200		{object}	store.Memo					"Memo information"
-//	@Failure	400		{object}	nil							"ID is not a number: %s | Malformatted post memo organizer request"
-//	@Failure	401		{object}	nil							"Missing user in session | Unauthorized"
-//	@Failure	404		{object}	nil							"Memo not found: %v"
-//	@Failure	500		{object}	nil							"Failed to find memo | Failed to upsert memo organizer | Failed to find memo by ID: %v | Failed to compose memo response"
-//	@Router		/api/v1/memo/{memoId}/organizer [POST]
-func (s *APIV1Service) CreateMemoOrganizer(c echo.Context) error {
-	ctx := c.Request().Context()
-	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
-	}
-
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
-		ID: &memoID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
-	}
-	if memo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %v", memoID))
-	}
-	if memo.CreatorID != userID {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	request := &UpsertMemoOrganizerRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo organizer request").SetInternal(err)
-	}
-
-	upsert := &store.MemoOrganizer{
-		MemoID: memoID,
-		UserID: userID,
-		Pinned: request.Pinned,
-	}
-	_, err = s.Store.UpsertMemoOrganizer(ctx, upsert)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo organizer").SetInternal(err)
-	}
-
-	memo, err = s.Store.GetMemo(ctx, &store.FindMemo{
-		ID: &memoID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find memo by ID: %v", memoID)).SetInternal(err)
-	}
-	if memo == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %v", memoID))
-	}
-
-	memoResponse, err := s.convertMemoFromStore(ctx, memo)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, memoResponse)
-}

programming_language: unknown
filename: memo_relation.go
change_type: ModificationType.DELETE
diff: @@ -1,156 +0,0 @@
-package v1
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-
-	"github.com/labstack/echo/v4"
-
-	"github.com/usememos/memos/internal/util"
-	"github.com/usememos/memos/store"
-)
-
-type MemoRelationType string
-
-const (
-	MemoRelationReference MemoRelationType = "REFERENCE"
-	MemoRelationComment   MemoRelationType = "COMMENT"
-)
-
-func (t MemoRelationType) String() string {
-	return string(t)
-}
-
-type MemoRelation struct {
-	MemoID        int32            `json:"memoId"`
-	RelatedMemoID int32            `json:"relatedMemoId"`
-	Type          MemoRelationType `json:"type"`
-}
-
-type UpsertMemoRelationRequest struct {
-	RelatedMemoID int32            `json:"relatedMemoId"`
-	Type          MemoRelationType `json:"type"`
-}
-
-func (s *APIV1Service) registerMemoRelationRoutes(g *echo.Group) {
-	g.GET("/memo/:memoId/relation", s.GetMemoRelationList)
-	g.POST("/memo/:memoId/relation", s.CreateMemoRelation)
-	g.DELETE("/memo/:memoId/relation/:relatedMemoId/type/:relationType", s.DeleteMemoRelation)
-}
-
-// GetMemoRelationList godoc
-//
-//	@Summary	Get a list of Memo Relations
-//	@Tags		memo-relation
-//	@Accept		json
-//	@Produce	json
-//	@Param		memoId	path		int						true	"ID of memo to find relations"
-//	@Success	200		{object}	[]store.MemoRelation	"Memo relation information list"
-//	@Failure	400		{object}	nil						"ID is not a number: %s"
-//	@Failure	500		{object}	nil						"Failed to list memo relations"
-//	@Router		/api/v1/memo/{memoId}/relation [GET]
-func (s *APIV1Service) GetMemoRelationList(c echo.Context) error {
-	ctx := c.Request().Context()
-	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
-	}
-
-	memoRelationList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
-		MemoID: &memoID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to list memo relations").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, memoRelationList)
-}
-
-// CreateMemoRelation godoc
-//
-//	@Summary		Create Memo Relation
-//	@Description	Create a relation between two memos
-//	@Tags			memo-relation
-//	@Accept			json
-//	@Produce		json
-//	@Param			memoId	path		int							true	"ID of memo to relate"
-//	@Param			body	body		UpsertMemoRelationRequest	true	"Memo relation object"
-//	@Success		200		{object}	store.MemoRelation			"Memo relation information"
-//	@Failure		400		{object}	nil							"ID is not a number: %s | Malformatted post memo relation request"
-//	@Failure		500		{object}	nil							"Failed to upsert memo relation"
-//	@Router			/api/v1/memo/{memoId}/relation [POST]
-//
-// NOTES:
-// - Currently not secured
-// - It's possible to create relations to memos that doesn't exist, which will trigger 404 errors when the frontend tries to load them.
-// - It's possible to create multiple relations, though the interface only shows first.
-func (s *APIV1Service) CreateMemoRelation(c echo.Context) error {
-	ctx := c.Request().Context()
-	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
-	}
-
-	request := &UpsertMemoRelationRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo relation request").SetInternal(err)
-	}
-
-	memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
-		MemoID:        memoID,
-		RelatedMemoID: request.RelatedMemoID,
-		Type:          store.MemoRelationType(request.Type),
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, memoRelation)
-}
-
-// DeleteMemoRelation godoc
-//
-//	@Summary		Delete a Memo Relation
-//	@Description	Removes a relation between two memos
-//	@Tags			memo-relation
-//	@Accept			json
-//	@Produce		json
-//	@Param			memoId			path		int					true	"ID of memo to find relations"
-//	@Param			relatedMemoId	path		int					true	"ID of memo to remove relation to"
-//	@Param			relationType	path		MemoRelationType	true	"Type of relation to remove"
-//	@Success		200				{boolean}	true				"Memo relation deleted"
-//	@Failure		400				{object}	nil					"Memo ID is not a number: %s | Related memo ID is not a number: %s"
-//	@Failure		500				{object}	nil					"Failed to delete memo relation"
-//	@Router			/api/v1/memo/{memoId}/relation/{relatedMemoId}/type/{relationType} [DELETE]
-//
-// NOTES:
-// - Currently not secured.
-// - Will always return true, even if the relation doesn't exist.
-func (s *APIV1Service) DeleteMemoRelation(c echo.Context) error {
-	ctx := c.Request().Context()
-	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Memo ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
-	}
-	relatedMemoID, err := util.ConvertStringToInt32(c.Param("relatedMemoId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Related memo ID is not a number: %s", c.Param("relatedMemoId"))).SetInternal(err)
-	}
-	relationType := store.MemoRelationType(c.Param("relationType"))
-
-	if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
-		MemoID:        &memoID,
-		RelatedMemoID: &relatedMemoID,
-		Type:          &relationType,
-	}); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete memo relation").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, true)
-}
-
-func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *MemoRelation {
-	return &MemoRelation{
-		MemoID:        memoRelation.MemoID,
-		RelatedMemoID: memoRelation.RelatedMemoID,
-		Type:          MemoRelationType(memoRelation.Type),
-	}
-}

programming_language: unknown
filename: resource.go
change_type: ModificationType.DELETE
diff: @@ -1,505 +0,0 @@
-package v1
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/labstack/echo/v4"
-	"github.com/lithammer/shortuuid/v4"
-	"github.com/pkg/errors"
-
-	"github.com/usememos/memos/internal/util"
-	"github.com/usememos/memos/plugin/storage/s3"
-	"github.com/usememos/memos/store"
-)
-
-type Resource struct {
-	ID   int32  `json:"id"`
-	Name string `json:"name"`
-	UID  string `json:"uid"`
-
-	// Standard fields
-	CreatorID int32 `json:"creatorId"`
-	CreatedTs int64 `json:"createdTs"`
-	UpdatedTs int64 `json:"updatedTs"`
-
-	// Domain specific fields
-	Filename     string `json:"filename"`
-	Blob         []byte `json:"-"`
-	InternalPath string `json:"-"`
-	ExternalLink string `json:"externalLink"`
-	Type         string `json:"type"`
-	Size         int64  `json:"size"`
-}
-
-type CreateResourceRequest struct {
-	Filename     string `json:"filename"`
-	ExternalLink string `json:"externalLink"`
-	Type         string `json:"type"`
-}
-
-type FindResourceRequest struct {
-	ID        *int32  `json:"id"`
-	CreatorID *int32  `json:"creatorId"`
-	Filename  *string `json:"filename"`
-}
-
-type UpdateResourceRequest struct {
-	Filename *string `json:"filename"`
-}
-
-const (
-	// The upload memory buffer is 32 MiB.
-	// It should be kept low, so RAM usage doesn't get out of control.
-	// This is unrelated to maximum upload size limit, which is now set through system setting.
-	maxUploadBufferSizeBytes = 32 << 20
-	MebiByte                 = 1024 * 1024
-)
-
-var fileKeyPattern = regexp.MustCompile(`\{[a-z]{1,9}\}`)
-
-func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
-	g.GET("/resource", s.GetResourceList)
-	g.POST("/resource", s.CreateResource)
-	g.POST("/resource/blob", s.UploadResource)
-	g.PATCH("/resource/:resourceId", s.UpdateResource)
-	g.DELETE("/resource/:resourceId", s.DeleteResource)
-}
-
-// GetResourceList godoc
-//
-//	@Summary	Get a list of resources
-//	@Tags		resource
-//	@Produce	json
-//	@Param		limit	query		int					false	"Limit"
-//	@Param		offset	query		int					false	"Offset"
-//	@Success	200		{object}	[]store.Resource	"Resource list"
-//	@Failure	401		{object}	nil					"Missing user in session"
-//	@Failure	500		{object}	nil					"Failed to fetch resource list"
-//	@Router		/api/v1/resource [GET]
-func (s *APIV1Service) GetResourceList(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-	find := &store.FindResource{
-		CreatorID: &userID,
-	}
-	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
-	}
-
-	list, err := s.Store.ListResources(ctx, find)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
-	}
-	resourceMessageList := []*Resource{}
-	for _, resource := range list {
-		resourceMessageList = append(resourceMessageList, convertResourceFromStore(resource))
-	}
-	return c.JSON(http.StatusOK, resourceMessageList)
-}
-
-// CreateResource godoc
-//
-//	@Summary	Create resource
-//	@Tags		resource
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		CreateResourceRequest	true	"Request object."
-//	@Success	200		{object}	store.Resource			"Created resource"
-//	@Failure	400		{object}	nil						"Malformatted post resource request | Invalid external link | Invalid external link scheme | Failed to request %s | Failed to read %s | Failed to read mime from %s"
-//	@Failure	401		{object}	nil						"Missing user in session"
-//	@Failure	500		{object}	nil						"Failed to save resource | Failed to create resource | Failed to create activity"
-//	@Router		/api/v1/resource [POST]
-func (s *APIV1Service) CreateResource(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	request := &CreateResourceRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post resource request").SetInternal(err)
-	}
-
-	create := &store.Resource{
-		UID:          shortuuid.New(),
-		CreatorID:    userID,
-		Filename:     request.Filename,
-		ExternalLink: request.ExternalLink,
-		Type:         request.Type,
-	}
-	if request.ExternalLink != "" {
-		// Only allow those external links scheme with http/https
-		linkURL, err := url.Parse(request.ExternalLink)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link").SetInternal(err)
-		}
-		if linkURL.Scheme != "http" && linkURL.Scheme != "https" {
-			return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link scheme")
-		}
-	}
-
-	resource, err := s.Store.CreateResource(ctx, create)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, convertResourceFromStore(resource))
-}
-
-// UploadResource godoc
-//
-//	@Summary	Upload resource
-//	@Tags		resource
-//	@Accept		multipart/form-data
-//	@Produce	json
-//	@Param		file	formData	file			true	"File to upload"
-//	@Success	200		{object}	store.Resource	"Created resource"
-//	@Failure	400		{object}	nil				"Upload file not found | File size exceeds allowed limit of %d MiB | Failed to parse upload data"
-//	@Failure	401		{object}	nil				"Missing user in session"
-//	@Failure	500		{object}	nil				"Failed to get uploading file | Failed to open file | Failed to save resource | Failed to create resource | Failed to create activity"
-//	@Router		/api/v1/resource/blob [POST]
-func (s *APIV1Service) UploadResource(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	maxUploadSetting, err := s.Store.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{Name: SystemSettingMaxUploadSizeMiBName.String()})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get max upload size").SetInternal(err)
-	}
-	var settingMaxUploadSizeBytes int
-	if maxUploadSetting != nil {
-		if settingMaxUploadSizeMiB, err := strconv.Atoi(maxUploadSetting.Value); err == nil {
-			settingMaxUploadSizeBytes = settingMaxUploadSizeMiB * MebiByte
-		} else {
-			settingMaxUploadSizeBytes = 0
-		}
-	} else {
-		// Default to 32 MiB.
-		settingMaxUploadSizeBytes = 32 * MebiByte
-	}
-
-	file, err := c.FormFile("file")
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get uploading file").SetInternal(err)
-	}
-	if file == nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Upload file not found").SetInternal(err)
-	}
-
-	if file.Size > int64(settingMaxUploadSizeBytes) {
-		message := fmt.Sprintf("File size exceeds allowed limit of %d MiB", settingMaxUploadSizeBytes/MebiByte)
-		return echo.NewHTTPError(http.StatusBadRequest, message).SetInternal(err)
-	}
-	if err := c.Request().ParseMultipartForm(maxUploadBufferSizeBytes); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Failed to parse upload data").SetInternal(err)
-	}
-
-	sourceFile, err := file.Open()
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to open file").SetInternal(err)
-	}
-	defer sourceFile.Close()
-
-	create := &store.Resource{
-		UID:       shortuuid.New(),
-		CreatorID: userID,
-		Filename:  file.Filename,
-		Type:      file.Header.Get("Content-Type"),
-		Size:      file.Size,
-	}
-	err = SaveResourceBlob(ctx, s.Store, create, sourceFile)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to save resource").SetInternal(err)
-	}
-
-	resource, err := s.Store.CreateResource(ctx, create)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, convertResourceFromStore(resource))
-}
-
-// DeleteResource godoc
-//
-//	@Summary	Delete a resource
-//	@Tags		resource
-//	@Produce	json
-//	@Param		resourceId	path		int		true	"Resource ID"
-//	@Success	200			{boolean}	true	"Resource deleted"
-//	@Failure	400			{object}	nil		"ID is not a number: %s"
-//	@Failure	401			{object}	nil		"Missing user in session"
-//	@Failure	404			{object}	nil		"Resource not found: %d"
-//	@Failure	500			{object}	nil		"Failed to find resource | Failed to delete resource"
-//	@Router		/api/v1/resource/{resourceId} [DELETE]
-func (s *APIV1Service) DeleteResource(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	resourceID, err := util.ConvertStringToInt32(c.Param("resourceId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
-	}
-
-	resource, err := s.Store.GetResource(ctx, &store.FindResource{
-		ID:        &resourceID,
-		CreatorID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
-	}
-	if resource == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Resource not found: %d", resourceID))
-	}
-
-	if err := s.Store.DeleteResource(ctx, &store.DeleteResource{
-		ID: resourceID,
-	}); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, true)
-}
-
-// UpdateResource godoc
-//
-//	@Summary	Update a resource
-//	@Tags		resource
-//	@Produce	json
-//	@Param		resourceId	path		int						true	"Resource ID"
-//	@Param		patch		body		UpdateResourceRequest	true	"Patch resource request"
-//	@Success	200			{object}	store.Resource			"Updated resource"
-//	@Failure	400			{object}	nil						"ID is not a number: %s | Malformatted patch resource request"
-//	@Failure	401			{object}	nil						"Missing user in session | Unauthorized"
-//	@Failure	404			{object}	nil						"Resource not found: %d"
-//	@Failure	500			{object}	nil						"Failed to find resource | Failed to patch resource"
-//	@Router		/api/v1/resource/{resourceId} [PATCH]
-func (s *APIV1Service) UpdateResource(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	resourceID, err := util.ConvertStringToInt32(c.Param("resourceId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
-	}
-
-	resource, err := s.Store.GetResource(ctx, &store.FindResource{
-		ID: &resourceID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
-	}
-	if resource == nil {
-		return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Resource not found: %d", resourceID))
-	}
-	if resource.CreatorID != userID {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	request := &UpdateResourceRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch resource request").SetInternal(err)
-	}
-
-	currentTs := time.Now().Unix()
-	update := &store.UpdateResource{
-		ID:        resourceID,
-		UpdatedTs: &currentTs,
-	}
-	if request.Filename != nil && *request.Filename != "" {
-		update.Filename = request.Filename
-	}
-
-	resource, err = s.Store.UpdateResource(ctx, update)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch resource").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, convertResourceFromStore(resource))
-}
-
-func replacePathTemplate(path, filename string) string {
-	t := time.Now()
-	path = fileKeyPattern.ReplaceAllStringFunc(path, func(s string) string {
-		switch s {
-		case "{filename}":
-			return filename
-		case "{timestamp}":
-			return fmt.Sprintf("%d", t.Unix())
-		case "{year}":
-			return fmt.Sprintf("%d", t.Year())
-		case "{month}":
-			return fmt.Sprintf("%02d", t.Month())
-		case "{day}":
-			return fmt.Sprintf("%02d", t.Day())
-		case "{hour}":
-			return fmt.Sprintf("%02d", t.Hour())
-		case "{minute}":
-			return fmt.Sprintf("%02d", t.Minute())
-		case "{second}":
-			return fmt.Sprintf("%02d", t.Second())
-		case "{uuid}":
-			return util.GenUUID()
-		}
-		return s
-	})
-	return path
-}
-
-func convertResourceFromStore(resource *store.Resource) *Resource {
-	return &Resource{
-		ID:           resource.ID,
-		Name:         fmt.Sprintf("resources/%d", resource.ID),
-		UID:          resource.UID,
-		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,
-	}
-}
-
-// SaveResourceBlob save the blob of resource based on the storage config
-//
-// Depend on the storage config, some fields of *store.ResourceCreate will be changed:
-// 1. *DatabaseStorage*: `create.Blob`.
-// 2. *LocalStorage*: `create.InternalPath`.
-// 3. Others( external service): `create.ExternalLink`.
-func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resource, r io.Reader) error {
-	systemSettingStorageServiceID, err := s.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{Name: SystemSettingStorageServiceIDName.String()})
-	if err != nil {
-		return errors.Wrap(err, "Failed to find SystemSettingStorageServiceIDName")
-	}
-
-	storageServiceID := DefaultStorage
-	if systemSettingStorageServiceID != nil {
-		err = json.Unmarshal([]byte(systemSettingStorageServiceID.Value), &storageServiceID)
-		if err != nil {
-			return errors.Wrap(err, "Failed to unmarshal storage service id")
-		}
-	}
-
-	// `DatabaseStorage` means store blob into database
-	if storageServiceID == DatabaseStorage {
-		fileBytes, err := io.ReadAll(r)
-		if err != nil {
-			return errors.Wrap(err, "Failed to read file")
-		}
-		create.Blob = fileBytes
-		return nil
-	} else if storageServiceID == LocalStorage {
-		// `LocalStorage` means save blob into local disk
-		systemSettingLocalStoragePath, err := s.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{Name: SystemSettingLocalStoragePathName.String()})
-		if err != nil {
-			return errors.Wrap(err, "Failed to find SystemSettingLocalStoragePathName")
-		}
-		localStoragePath := "assets/{timestamp}_{filename}"
-		if systemSettingLocalStoragePath != nil && systemSettingLocalStoragePath.Value != "" {
-			err = json.Unmarshal([]byte(systemSettingLocalStoragePath.Value), &localStoragePath)
-			if err != nil {
-				return errors.Wrap(err, "Failed to unmarshal SystemSettingLocalStoragePathName")
-			}
-		}
-
-		internalPath := localStoragePath
-		if !strings.Contains(internalPath, "{filename}") {
-			internalPath = filepath.Join(internalPath, "{filename}")
-		}
-		internalPath = replacePathTemplate(internalPath, create.Filename)
-		internalPath = filepath.ToSlash(internalPath)
-		create.InternalPath = internalPath
-
-		osPath := filepath.FromSlash(internalPath)
-		if !filepath.IsAbs(osPath) {
-			osPath = filepath.Join(s.Profile.Data, osPath)
-		}
-		dir := filepath.Dir(osPath)
-		if err = os.MkdirAll(dir, os.ModePerm); err != nil {
-			return errors.Wrap(err, "Failed to create directory")
-		}
-		dst, err := os.Create(osPath)
-		if err != nil {
-			return errors.Wrap(err, "Failed to create file")
-		}
-		defer dst.Close()
-		_, err = io.Copy(dst, r)
-		if err != nil {
-			return errors.Wrap(err, "Failed to copy file")
-		}
-
-		return nil
-	}
-
-	// Others: store blob into external service, such as S3
-	storage, err := s.GetStorage(ctx, &store.FindStorage{ID: &storageServiceID})
-	if err != nil {
-		return errors.Wrap(err, "Failed to find StorageServiceID")
-	}
-	if storage == nil {
-		return errors.Errorf("Storage %d not found", storageServiceID)
-	}
-	storageMessage, err := ConvertStorageFromStore(storage)
-	if err != nil {
-		return errors.Wrap(err, "Failed to ConvertStorageFromStore")
-	}
-
-	if storageMessage.Type != StorageS3 {
-		return errors.Errorf("Unsupported storage type: %s", storageMessage.Type)
-	}
-
-	s3Config := storageMessage.Config.S3Config
-	s3Client, err := s3.NewClient(ctx, &s3.Config{
-		AccessKey: s3Config.AccessKey,
-		SecretKey: s3Config.SecretKey,
-		EndPoint:  s3Config.EndPoint,
-		Region:    s3Config.Region,
-		Bucket:    s3Config.Bucket,
-		URLPrefix: s3Config.URLPrefix,
-		URLSuffix: s3Config.URLSuffix,
-		PreSign:   s3Config.PreSign,
-	})
-	if err != nil {
-		return errors.Wrap(err, "Failed to create s3 client")
-	}
-
-	filePath := s3Config.Path
-	if !strings.Contains(filePath, "{filename}") {
-		filePath = filepath.Join(filePath, "{filename}")
-	}
-	filePath = replacePathTemplate(filePath, create.Filename)
-
-	link, err := s3Client.UploadFile(ctx, filePath, create.Type, r)
-	if err != nil {
-		return errors.Wrap(err, "Failed to upload via s3 client")
-	}
-
-	create.ExternalLink = link
-	return nil
-}

programming_language: unknown
filename: storage.go
change_type: ModificationType.DELETE
diff: @@ -1,316 +0,0 @@
-package v1
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-
-	"github.com/labstack/echo/v4"
-
-	"github.com/usememos/memos/internal/util"
-	"github.com/usememos/memos/store"
-)
-
-const (
-	// LocalStorage means the storage service is local file system.
-	LocalStorage int32 = -1
-	// DatabaseStorage means the storage service is database.
-	DatabaseStorage int32 = 0
-	// Default storage service is database.
-	DefaultStorage int32 = DatabaseStorage
-)
-
-type StorageType string
-
-const (
-	StorageS3 StorageType = "S3"
-)
-
-func (t StorageType) String() string {
-	return string(t)
-}
-
-type StorageConfig struct {
-	S3Config *StorageS3Config `json:"s3Config"`
-}
-
-type StorageS3Config struct {
-	EndPoint  string `json:"endPoint"`
-	Path      string `json:"path"`
-	Region    string `json:"region"`
-	AccessKey string `json:"accessKey"`
-	SecretKey string `json:"secretKey"`
-	Bucket    string `json:"bucket"`
-	URLPrefix string `json:"urlPrefix"`
-	URLSuffix string `json:"urlSuffix"`
-	PreSign   bool   `json:"presign"`
-}
-
-type Storage struct {
-	ID     int32          `json:"id"`
-	Name   string         `json:"name"`
-	Type   StorageType    `json:"type"`
-	Config *StorageConfig `json:"config"`
-}
-
-type CreateStorageRequest struct {
-	Name   string         `json:"name"`
-	Type   StorageType    `json:"type"`
-	Config *StorageConfig `json:"config"`
-}
-
-type UpdateStorageRequest struct {
-	Type   StorageType    `json:"type"`
-	Name   *string        `json:"name"`
-	Config *StorageConfig `json:"config"`
-}
-
-func (s *APIV1Service) registerStorageRoutes(g *echo.Group) {
-	g.GET("/storage", s.GetStorageList)
-	g.POST("/storage", s.CreateStorage)
-	g.PATCH("/storage/:storageId", s.UpdateStorage)
-	g.DELETE("/storage/:storageId", s.DeleteStorage)
-}
-
-// GetStorageList godoc
-//
-//	@Summary	Get a list of storages
-//	@Tags		storage
-//	@Produce	json
-//	@Success	200	{object}	[]store.Storage	"List of storages"
-//	@Failure	401	{object}	nil				"Missing user in session | Unauthorized"
-//	@Failure	500	{object}	nil				"Failed to find user | Failed to convert storage"
-//	@Router		/api/v1/storage [GET]
-func (s *APIV1Service) GetStorageList(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	// We should only show storage list to host user.
-	if user == nil || user.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	list, err := s.Store.ListStorages(ctx, &store.FindStorage{})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage list").SetInternal(err)
-	}
-
-	storageList := []*Storage{}
-	for _, storage := range list {
-		storageMessage, err := ConvertStorageFromStore(storage)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
-		}
-		storageList = append(storageList, storageMessage)
-	}
-	return c.JSON(http.StatusOK, storageList)
-}
-
-// CreateStorage godoc
-//
-//	@Summary	Create storage
-//	@Tags		storage
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		CreateStorageRequest	true	"Request object."
-//	@Success	200		{object}	store.Storage			"Created storage"
-//	@Failure	400		{object}	nil						"Malformatted post storage request"
-//	@Failure	401		{object}	nil						"Missing user in session"
-//	@Failure	500		{object}	nil						"Failed to find user | Failed to create storage | Failed to convert storage"
-//	@Router		/api/v1/storage [POST]
-func (s *APIV1Service) CreateStorage(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil || user.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	create := &CreateStorageRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(create); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
-	}
-
-	configString := ""
-	if create.Type == StorageS3 && create.Config.S3Config != nil {
-		configBytes, err := json.Marshal(create.Config.S3Config)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
-		}
-		configString = string(configBytes)
-	}
-
-	storage, err := s.Store.CreateStorage(ctx, &store.Storage{
-		Name:   create.Name,
-		Type:   create.Type.String(),
-		Config: configString,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create storage").SetInternal(err)
-	}
-	storageMessage, err := ConvertStorageFromStore(storage)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, storageMessage)
-}
-
-// DeleteStorage godoc
-//
-//	@Summary	Delete a storage
-//	@Tags		storage
-//	@Produce	json
-//	@Param		storageId	path		int		true	"Storage ID"
-//	@Success	200			{boolean}	true	"Storage deleted"
-//	@Failure	400			{object}	nil		"ID is not a number: %s | Storage service %d is using"
-//	@Failure	401			{object}	nil		"Missing user in session | Unauthorized"
-//	@Failure	500			{object}	nil		"Failed to find user | Failed to find storage | Failed to unmarshal storage service id | Failed to delete storage"
-//	@Router		/api/v1/storage/{storageId} [DELETE]
-//
-// NOTES:
-// - error message "Storage service %d is using" probably should be "Storage service %d is in use".
-func (s *APIV1Service) DeleteStorage(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil || user.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	storageID, err := util.ConvertStringToInt32(c.Param("storageId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("storageId"))).SetInternal(err)
-	}
-
-	systemSetting, err := s.Store.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{Name: SystemSettingStorageServiceIDName.String()})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
-	}
-	if systemSetting != nil {
-		storageServiceID := DefaultStorage
-		err = json.Unmarshal([]byte(systemSetting.Value), &storageServiceID)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal storage service id").SetInternal(err)
-		}
-		if storageServiceID == storageID {
-			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Storage service %d is using", storageID))
-		}
-	}
-
-	if err = s.Store.DeleteStorage(ctx, &store.DeleteStorage{ID: storageID}); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete storage").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, true)
-}
-
-// UpdateStorage godoc
-//
-//	@Summary	Update a storage
-//	@Tags		storage
-//	@Produce	json
-//	@Param		storageId	path		int						true	"Storage ID"
-//	@Param		patch		body		UpdateStorageRequest	true	"Patch request"
-//	@Success	200			{object}	store.Storage			"Updated resource"
-//	@Failure	400			{object}	nil						"ID is not a number: %s | Malformatted patch storage request | Malformatted post storage request"
-//	@Failure	401			{object}	nil						"Missing user in session | Unauthorized"
-//	@Failure	500			{object}	nil						"Failed to find user | Failed to patch storage | Failed to convert storage"
-//	@Router		/api/v1/storage/{storageId} [PATCH]
-func (s *APIV1Service) UpdateStorage(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil || user.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	storageID, err := util.ConvertStringToInt32(c.Param("storageId"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("storageId"))).SetInternal(err)
-	}
-
-	update := &UpdateStorageRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(update); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch storage request").SetInternal(err)
-	}
-	storageUpdate := &store.UpdateStorage{
-		ID: storageID,
-	}
-	if update.Name != nil {
-		storageUpdate.Name = update.Name
-	}
-	if update.Config != nil {
-		if update.Type == StorageS3 {
-			configBytes, err := json.Marshal(update.Config.S3Config)
-			if err != nil {
-				return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
-			}
-			configString := string(configBytes)
-			storageUpdate.Config = &configString
-		}
-	}
-
-	storage, err := s.Store.UpdateStorage(ctx, storageUpdate)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch storage").SetInternal(err)
-	}
-	storageMessage, err := ConvertStorageFromStore(storage)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, storageMessage)
-}
-
-func ConvertStorageFromStore(storage *store.Storage) (*Storage, error) {
-	storageMessage := &Storage{
-		ID:     storage.ID,
-		Name:   storage.Name,
-		Type:   StorageType(storage.Type),
-		Config: &StorageConfig{},
-	}
-	if storageMessage.Type == StorageS3 {
-		s3Config := &StorageS3Config{}
-		if err := json.Unmarshal([]byte(storage.Config), s3Config); err != nil {
-			return nil, err
-		}
-		storageMessage.Config = &StorageConfig{
-			S3Config: s3Config,
-		}
-	}
-	return storageMessage, nil
-}

programming_language: unknown
filename: swagger.md
change_type: ModificationType.DELETE
diff: @@ -1,1708 +0,0 @@
-# memos API
-A privacy-first, lightweight note-taking service.
-
-## Version: 1.0
-
-**Contact information:**  
-API Support  
-https://github.com/orgs/usememos/discussions  
-
-**License:** [MIT License](https://github.com/usememos/memos/blob/main/LICENSE)
-
-[Find out more about Memos.](https://usememos.com/)
-
----
-### /api/v1/auth/signin
-
-#### POST
-##### Summary
-
-Sign-in to memos.
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Sign-in object | Yes | [github_com_usememos_memos_api_v1.SignIn](#github_com_usememos_memos_api_v1signin) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | User information | [store.User](#storeuser) |
-| 400 | Malformatted signin request |  |
-| 401 | Password login is deactivated \| Incorrect login credentials, please try again |  |
-| 403 | User has been archived with username %s |  |
-| 500 | Failed to find system setting \| Failed to unmarshal system setting \| Incorrect login credentials, please try again \| Failed to generate tokens \| Failed to create activity |  |
-
-### /api/v1/auth/signin/sso
-
-#### POST
-##### Summary
-
-Sign-in to memos using SSO.
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | SSO sign-in object | Yes | [github_com_usememos_memos_api_v1.SSOSignIn](#github_com_usememos_memos_api_v1ssosignin) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | User information | [store.User](#storeuser) |
-| 400 | Malformatted signin request |  |
-| 401 | Access denied, identifier does not match the filter. |  |
-| 403 | User has been archived with username {username} |  |
-| 404 | Identity provider not found |  |
-| 500 | Failed to find identity provider \| Failed to create identity provider instance \| Failed to exchange token \| Failed to get user info \| Failed to compile identifier filter \| Incorrect login credentials, please try again \| Failed to generate random password \| Failed to generate password hash \| Failed to create user \| Failed to generate tokens \| Failed to create activity |  |
-
-### /api/v1/auth/signout
-
-#### POST
-##### Summary
-
-Sign-out from memos.
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Sign-out success | boolean |
-
-### /api/v1/auth/signup
-
-#### POST
-##### Summary
-
-Sign-up to memos.
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Sign-up object | Yes | [github_com_usememos_memos_api_v1.SignUp](#github_com_usememos_memos_api_v1signup) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | User information | [store.User](#storeuser) |
-| 400 | Malformatted signup request \| Failed to find users |  |
-| 401 | signup is disabled |  |
-| 403 | Forbidden |  |
-| 404 | Not found |  |
-| 500 | Failed to find system setting \| Failed to unmarshal system setting allow signup \| Failed to generate password hash \| Failed to create user \| Failed to generate tokens \| Failed to create activity |  |
-
----
-### /api/v1/idp
-
-#### GET
-##### Summary
-
-Get a list of identity providers
-
-##### Description
-
-*clientSecret is only available for host user
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | List of available identity providers | [ [api_v1.IdentityProvider](#api_v1identityprovider) ] |
-| 500 | Failed to find identity provider list \| Failed to find user |  |
-
-#### POST
-##### Summary
-
-Create Identity Provider
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Identity provider information | Yes | [api_v1.CreateIdentityProviderRequest](#api_v1createidentityproviderrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Identity provider information | [store.IdentityProvider](#storeidentityprovider) |
-| 400 | Malformatted post identity provider request |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to create identity provider |  |
-
-### /api/v1/idp/{idpId}
-
-#### DELETE
-##### Summary
-
-Delete an identity provider by ID
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| idpId | path | Identity Provider ID | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Identity Provider deleted | boolean |
-| 400 | ID is not a number: %s \| Malformatted patch identity provider request |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to patch identity provider |  |
-
-#### GET
-##### Summary
-
-Get an identity provider by ID
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| idpId | path | Identity provider ID | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Requested identity provider | [store.IdentityProvider](#storeidentityprovider) |
-| 400 | ID is not a number: %s |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 404 | Identity provider not found |  |
-| 500 | Failed to find identity provider list \| Failed to find user |  |
-
-#### PATCH
-##### Summary
-
-Update an identity provider by ID
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| idpId | path | Identity Provider ID | Yes | integer |
-| body | body | Patched identity provider information | Yes | [api_v1.UpdateIdentityProviderRequest](#api_v1updateidentityproviderrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Patched identity provider | [store.IdentityProvider](#storeidentityprovider) |
-| 400 | ID is not a number: %s \| Malformatted patch identity provider request |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to patch identity provider |  |
-
----
-### /api/v1/memo
-
-#### GET
-##### Summary
-
-Get a list of memos matching optional filters
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| creatorId | query | Creator ID | No | integer |
-| creatorUsername | query | Creator username | No | string |
-| rowStatus | query | Row status | No | string |
-| pinned | query | Pinned | No | boolean |
-| tag | query | Search for tag. Do not append # | No | string |
-| content | query | Search for content | No | string |
-| limit | query | Limit | No | integer |
-| offset | query | Offset | No | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo list | [ [store.Memo](#storememo) ] |
-| 400 | Missing user to find memo |  |
-| 500 | Failed to get memo display with updated ts setting value \| Failed to fetch memo list \| Failed to compose memo response |  |
-
-#### POST
-##### Summary
-
-Create a memo
-
-##### Description
-
-Visibility can be PUBLIC, PROTECTED or PRIVATE
-*You should omit fields to use their default values
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Request object. | Yes | [github_com_usememos_memos_api_v1.CreateMemoRequest](#github_com_usememos_memos_api_v1creatememorequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Stored memo | [store.Memo](#storememo) |
-| 400 | Malformatted post memo request \| Content size overflow, up to 1MB |  |
-| 401 | Missing user in session |  |
-| 404 | User not found \| Memo not found: %d |  |
-| 500 | Failed to find user setting \| Failed to unmarshal user setting value \| Failed to find system setting \| Failed to unmarshal system setting \| Failed to find user \| Failed to create memo \| Failed to create activity \| Failed to upsert memo resource \| Failed to upsert memo relation \| Failed to compose memo \| Failed to compose memo response |  |
-
-### /api/v1/memo/{memoId}
-
-#### DELETE
-##### Summary
-
-Delete memo by ID
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| memoId | path | Memo ID to delete | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo deleted | boolean |
-| 400 | ID is not a number: %s |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 404 | Memo not found: %d |  |
-| 500 | Failed to find memo \| Failed to delete memo ID: %v |  |
-
-#### GET
-##### Summary
-
-Get memo by ID
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| memoId | path | Memo ID | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo list | [ [store.Memo](#storememo) ] |
-| 400 | ID is not a number: %s |  |
-| 401 | Missing user in session |  |
-| 403 | this memo is private only \| this memo is protected, missing user in session |  |
-| 404 | Memo not found: %d |  |
-| 500 | Failed to find memo by ID: %v \| Failed to compose memo response |  |
-
-#### PATCH
-##### Summary
-
-Update a memo
-
-##### Description
-
-Visibility can be PUBLIC, PROTECTED or PRIVATE
-*You should omit fields to use their default values
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| memoId | path | ID of memo to update | Yes | integer |
-| body | body | Patched object. | Yes | [github_com_usememos_memos_api_v1.PatchMemoRequest](#github_com_usememos_memos_api_v1patchmemorequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Stored memo | [store.Memo](#storememo) |
-| 400 | ID is not a number: %s \| Malformatted patch memo request \| Content size overflow, up to 1MB |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 404 | Memo not found: %d |  |
-| 500 | Failed to find memo \| Failed to patch memo \| Failed to upsert memo resource \| Failed to delete memo resource \| Failed to compose memo response |  |
-
-### /api/v1/memo/all
-
-#### GET
-##### Summary
-
-Get a list of public memos matching optional filters
-
-##### Description
-
-This should also list protected memos if the user is logged in
-Authentication is optional
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| limit | query | Limit | No | integer |
-| offset | query | Offset | No | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo list | [ [store.Memo](#storememo) ] |
-| 500 | Failed to get memo display with updated ts setting value \| Failed to fetch all memo list \| Failed to compose memo response |  |
-
-### /api/v1/memo/stats
-
-#### GET
-##### Summary
-
-Get memo stats by creator ID or username
-
-##### Description
-
-Used to generate the heatmap
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| creatorId | query | Creator ID | No | integer |
-| creatorUsername | query | Creator username | No | string |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo createdTs list | [ integer ] |
-| 400 | Missing user id to find memo |  |
-| 500 | Failed to get memo display with updated ts setting value \| Failed to find memo list \| Failed to compose memo response |  |
-
----
-### /api/v1/memo/{memoId}/organizer
-
-#### POST
-##### Summary
-
-Organize memo (pin/unpin)
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| memoId | path | ID of memo to organize | Yes | integer |
-| body | body | Memo organizer object | Yes | [github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest](#github_com_usememos_memos_api_v1upsertmemoorganizerrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo information | [store.Memo](#storememo) |
-| 400 | ID is not a number: %s \| Malformatted post memo organizer request |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 404 | Memo not found: %v |  |
-| 500 | Failed to find memo \| Failed to upsert memo organizer \| Failed to find memo by ID: %v \| Failed to compose memo response |  |
-
----
-### /api/v1/memo/{memoId}/relation
-
-#### GET
-##### Summary
-
-Get a list of Memo Relations
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| memoId | path | ID of memo to find relations | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo relation information list | [ [store.MemoRelation](#storememorelation) ] |
-| 400 | ID is not a number: %s |  |
-| 500 | Failed to list memo relations |  |
-
-#### POST
-##### Summary
-
-Create Memo Relation
-
-##### Description
-
-Create a relation between two memos
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| memoId | path | ID of memo to relate | Yes | integer |
-| body | body | Memo relation object | Yes | [github_com_usememos_memos_api_v1.UpsertMemoRelationRequest](#github_com_usememos_memos_api_v1upsertmemorelationrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo relation information | [store.MemoRelation](#storememorelation) |
-| 400 | ID is not a number: %s \| Malformatted post memo relation request |  |
-| 500 | Failed to upsert memo relation |  |
-
-### /api/v1/memo/{memoId}/relation/{relatedMemoId}/type/{relationType}
-
-#### DELETE
-##### Summary
-
-Delete a Memo Relation
-
-##### Description
-
-Removes a relation between two memos
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| memoId | path | ID of memo to find relations | Yes | integer |
-| relatedMemoId | path | ID of memo to remove relation to | Yes | integer |
-| relationType | path | Type of relation to remove | Yes | string |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Memo relation deleted | boolean |
-| 400 | Memo ID is not a number: %s \| Related memo ID is not a number: %s |  |
-| 500 | Failed to delete memo relation |  |
-
----
-### /api/v1/ping
-
-#### GET
-##### Summary
-
-Ping the system
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | If succeed to ping the system | boolean |
-
-### /api/v1/status
-
-#### GET
-##### Summary
-
-Get system GetSystemStatus
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | System GetSystemStatus | [api_v1.SystemStatus](#api_v1systemstatus) |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find host user \| Failed to find system setting list \| Failed to unmarshal system setting customized profile value |  |
-
-### /api/v1/system/vacuum
-
-#### POST
-##### Summary
-
-Vacuum the database
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Database vacuumed | boolean |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to ExecVacuum database |  |
-
----
-### /api/v1/resource
-
-#### GET
-##### Summary
-
-Get a list of resources
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| limit | query | Limit | No | integer |
-| offset | query | Offset | No | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Resource list | [ [store.Resource](#storeresource) ] |
-| 401 | Missing user in session |  |
-| 500 | Failed to fetch resource list |  |
-
-#### POST
-##### Summary
-
-Create resource
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Request object. | Yes | [api_v1.CreateResourceRequest](#api_v1createresourcerequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Created resource | [store.Resource](#storeresource) |
-| 400 | Malformatted post resource request \| Invalid external link \| Invalid external link scheme \| Failed to request %s \| Failed to read %s \| Failed to read mime from %s |  |
-| 401 | Missing user in session |  |
-| 500 | Failed to save resource \| Failed to create resource \| Failed to create activity |  |
-
-### /api/v1/resource/{resourceId}
-
-#### DELETE
-##### Summary
-
-Delete a resource
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| resourceId | path | Resource ID | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Resource deleted | boolean |
-| 400 | ID is not a number: %s |  |
-| 401 | Missing user in session |  |
-| 404 | Resource not found: %d |  |
-| 500 | Failed to find resource \| Failed to delete resource |  |
-
-#### PATCH
-##### Summary
-
-Update a resource
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| resourceId | path | Resource ID | Yes | integer |
-| patch | body | Patch resource request | Yes | [api_v1.UpdateResourceRequest](#api_v1updateresourcerequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Updated resource | [store.Resource](#storeresource) |
-| 400 | ID is not a number: %s \| Malformatted patch resource request |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 404 | Resource not found: %d |  |
-| 500 | Failed to find resource \| Failed to patch resource |  |
-
-### /api/v1/resource/blob
-
-#### POST
-##### Summary
-
-Upload resource
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| file | formData | File to upload | Yes | file |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Created resource | [store.Resource](#storeresource) |
-| 400 | Upload file not found \| File size exceeds allowed limit of %d MiB \| Failed to parse upload data |  |
-| 401 | Missing user in session |  |
-| 500 | Failed to get uploading file \| Failed to open file \| Failed to save resource \| Failed to create resource \| Failed to create activity |  |
-
----
-### /api/v1/storage
-
-#### GET
-##### Summary
-
-Get a list of storages
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | List of storages | [ [store.Storage](#storestorage) ] |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to convert storage |  |
-
-#### POST
-##### Summary
-
-Create storage
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Request object. | Yes | [github_com_usememos_memos_api_v1.CreateStorageRequest](#github_com_usememos_memos_api_v1createstoragerequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Created storage | [store.Storage](#storestorage) |
-| 400 | Malformatted post storage request |  |
-| 401 | Missing user in session |  |
-| 500 | Failed to find user \| Failed to create storage \| Failed to convert storage |  |
-
-### /api/v1/storage/{storageId}
-
-#### DELETE
-##### Summary
-
-Delete a storage
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| storageId | path | Storage ID | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Storage deleted | boolean |
-| 400 | ID is not a number: %s \| Storage service %d is using |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to find storage \| Failed to unmarshal storage service id \| Failed to delete storage |  |
-
-#### PATCH
-##### Summary
-
-Update a storage
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| storageId | path | Storage ID | Yes | integer |
-| patch | body | Patch request | Yes | [github_com_usememos_memos_api_v1.UpdateStorageRequest](#github_com_usememos_memos_api_v1updatestoragerequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Updated resource | [store.Storage](#storestorage) |
-| 400 | ID is not a number: %s \| Malformatted patch storage request \| Malformatted post storage request |  |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to patch storage \| Failed to convert storage |  |
-
----
-### /api/v1/system/setting
-
-#### GET
-##### Summary
-
-Get a list of system settings
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | System setting list | [ [api_v1.SystemSetting](#api_v1systemsetting) ] |
-| 401 | Missing user in session \| Unauthorized |  |
-| 500 | Failed to find user \| Failed to find system setting list |  |
-
-#### POST
-##### Summary
-
-Create system setting
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Request object. | Yes | [api_v1.UpsertSystemSettingRequest](#api_v1upsertsystemsettingrequest) |
-
-##### Responses
-
-| Code | Description |
-| ---- | ----------- |
-| 400 | Malformatted post system setting request \| invalid system setting |
-| 401 | Missing user in session \| Unauthorized |
-| 403 | Cannot disable passwords if no SSO identity provider is configured. |
-| 500 | Failed to find user \| Failed to upsert system setting |
-
----
-### /api/v1/tag
-
-#### GET
-##### Summary
-
-Get a list of tags
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Tag list | [ string ] |
-| 400 | Missing user id to find tag |  |
-| 500 | Failed to find tag list |  |
-
-#### POST
-##### Summary
-
-Create a tag
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Request object. | Yes | [github_com_usememos_memos_api_v1.UpsertTagRequest](#github_com_usememos_memos_api_v1upserttagrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Created tag name | string |
-| 400 | Malformatted post tag request \| Tag name shouldn't be empty |  |
-| 401 | Missing user in session |  |
-| 500 | Failed to upsert tag \| Failed to create activity |  |
-
-### /api/v1/tag/delete
-
-#### POST
-##### Summary
-
-Delete a tag
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Request object. | Yes | [github_com_usememos_memos_api_v1.DeleteTagRequest](#github_com_usememos_memos_api_v1deletetagrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Tag deleted | boolean |
-| 400 | Malformatted post tag request \| Tag name shouldn't be empty |  |
-| 401 | Missing user in session |  |
-| 500 | Failed to delete tag name: %v |  |
-
-### /api/v1/tag/suggestion
-
-#### GET
-##### Summary
-
-Get a list of tags suggested from other memos contents
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Tag list | [ string ] |
-| 400 | Missing user session |  |
-| 500 | Failed to find memo list \| Failed to find tag list |  |
-
----
-### /api/v1/user
-
-#### GET
-##### Summary
-
-Get a list of users
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | User list | [ [store.User](#storeuser) ] |
-| 500 | Failed to fetch user list |  |
-
-#### POST
-##### Summary
-
-Create a user
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| body | body | Request object | Yes | [api_v1.CreateUserRequest](#api_v1createuserrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Created user | [store.User](#storeuser) |
-| 400 | Malformatted post user request \| Invalid user create format |  |
-| 401 | Missing auth session \| Unauthorized to create user |  |
-| 403 | Could not create host user |  |
-| 500 | Failed to find user by id \| Failed to generate password hash \| Failed to create user \| Failed to create activity |  |
-
-### /api/v1/user/{id}
-
-#### DELETE
-##### Summary
-
-Delete a user
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| id | path | User ID | Yes | string |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | User deleted | boolean |
-| 400 | ID is not a number: %s \| Current session user not found with ID: %d |  |
-| 401 | Missing user in session |  |
-| 403 | Unauthorized to delete user |  |
-| 500 | Failed to find user \| Failed to delete user |  |
-
-#### GET
-##### Summary
-
-Get user by id
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| id | path | User ID | Yes | integer |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Requested user | [store.User](#storeuser) |
-| 400 | Malformatted user id |  |
-| 404 | User not found |  |
-| 500 | Failed to find user |  |
-
-#### PATCH
-##### Summary
-
-Update a user
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| id | path | User ID | Yes | string |
-| patch | body | Patch request | Yes | [api_v1.UpdateUserRequest](#api_v1updateuserrequest) |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Updated user | [store.User](#storeuser) |
-| 400 | ID is not a number: %s \| Current session user not found with ID: %d \| Malformatted patch user request \| Invalid update user request |  |
-| 401 | Missing user in session |  |
-| 403 | Unauthorized to update user |  |
-| 500 | Failed to find user \| Failed to generate password hash \| Failed to patch user \| Failed to find userSettingList |  |
-
-### /api/v1/user/me
-
-#### GET
-##### Summary
-
-Get current user
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Current user | [store.User](#storeuser) |
-| 401 | Missing auth session |  |
-| 500 | Failed to find user \| Failed to find userSettingList |  |
-
-### /api/v1/user/name/{username}
-
-#### GET
-##### Summary
-
-Get user by username
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| username | path | Username | Yes | string |
-
-##### Responses
-
-| Code | Description | Schema |
-| ---- | ----------- | ------ |
-| 200 | Requested user | [store.User](#storeuser) |
-| 404 | User not found |  |
-| 500 | Failed to find user |  |
-
----
-### /o/get/GetImage
-
-#### GET
-##### Summary
-
-Get GetImage from URL
-
-##### Parameters
-
-| Name | Located in | Description | Required | Schema |
-| ---- | ---------- | ----------- | -------- | ------ |
-| url | query | Image url | Yes | string |
-
-##### Responses
-
-| Code | Description |
-| ---- | ----------- |
-| 200 | Image |
-| 400 | Missing GetImage url \| Wrong url \| Failed to get GetImage url: %s |
-| 500 | Failed to write GetImage blob |
-
----
-### Models
-
-#### api_v1.CreateIdentityProviderRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [api_v1.IdentityProviderConfig](#api_v1identityproviderconfig) |  | No |
-| identifierFilter | string |  | No |
-| name | string |  | No |
-| type | [api_v1.IdentityProviderType](#api_v1identityprovidertype) |  | No |
-
-#### api_v1.CreateMemoRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| content | string |  | No |
-| createdTs | integer |  | No |
-| relationList | [ [api_v1.UpsertMemoRelationRequest](#api_v1upsertmemorelationrequest) ] |  | No |
-| resourceIdList | [ integer ] | Related fields | No |
-| visibility | [api_v1.Visibility](#api_v1visibility) | Domain specific fields | No |
-
-#### api_v1.CreateResourceRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| externalLink | string |  | No |
-| filename | string |  | No |
-| type | string |  | No |
-
-#### api_v1.CreateStorageRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [api_v1.StorageConfig](#api_v1storageconfig) |  | No |
-| name | string |  | No |
-| type | [api_v1.StorageType](#api_v1storagetype) |  | No |
-
-#### api_v1.CreateUserRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| email | string |  | No |
-| nickname | string |  | No |
-| password | string |  | No |
-| role | [api_v1.Role](#api_v1role) |  | No |
-| username | string |  | No |
-
-#### api_v1.CustomizedProfile
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| appearance | string | Appearance is the server default appearance. | No |
-| description | string | Description is the server description. | No |
-| locale | string | Locale is the server default locale. | No |
-| logoUrl | string | LogoURL is the url of logo image. | No |
-| name | string | Name is the server name, default is `memos` | No |
-
-#### api_v1.DeleteTagRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| name | string |  | No |
-
-#### api_v1.FieldMapping
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| displayName | string |  | No |
-| email | string |  | No |
-| identifier | string |  | No |
-
-#### api_v1.IdentityProvider
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [api_v1.IdentityProviderConfig](#api_v1identityproviderconfig) |  | No |
-| id | integer |  | No |
-| identifierFilter | string |  | No |
-| name | string |  | No |
-| type | [api_v1.IdentityProviderType](#api_v1identityprovidertype) |  | No |
-
-#### api_v1.IdentityProviderConfig
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| oauth2Config | [api_v1.IdentityProviderOAuth2Config](#api_v1identityprovideroauth2config) |  | No |
-
-#### api_v1.IdentityProviderOAuth2Config
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| authUrl | string |  | No |
-| clientId | string |  | No |
-| clientSecret | string |  | No |
-| fieldMapping | [api_v1.FieldMapping](#api_v1fieldmapping) |  | No |
-| scopes | [ string ] |  | No |
-| tokenUrl | string |  | No |
-| userInfoUrl | string |  | No |
-
-#### api_v1.IdentityProviderType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| api_v1.IdentityProviderType | string |  |  |
-
-#### api_v1.MemoRelationType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| api_v1.MemoRelationType | string |  |  |
-
-#### api_v1.PatchMemoRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| content | string | Domain specific fields | No |
-| createdTs | integer | Standard fields | No |
-| relationList | [ [api_v1.UpsertMemoRelationRequest](#api_v1upsertmemorelationrequest) ] |  | No |
-| resourceIdList | [ integer ] | Related fields | No |
-| rowStatus | [api_v1.RowStatus](#api_v1rowstatus) |  | No |
-| updatedTs | integer |  | No |
-| visibility | [api_v1.Visibility](#api_v1visibility) |  | No |
-
-#### api_v1.Role
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| api_v1.Role | string |  |  |
-
-#### api_v1.RowStatus
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| api_v1.RowStatus | string |  |  |
-
-#### api_v1.SSOSignIn
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| code | string |  | No |
-| identityProviderId | integer |  | No |
-| redirectUri | string |  | No |
-
-#### api_v1.SignIn
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| password | string |  | No |
-| remember | boolean |  | No |
-| username | string |  | No |
-
-#### api_v1.SignUp
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| password | string |  | No |
-| username | string |  | No |
-
-#### api_v1.StorageConfig
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| s3Config | [api_v1.StorageS3Config](#api_v1storages3config) |  | No |
-
-#### api_v1.StorageS3Config
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| accessKey | string |  | No |
-| bucket | string |  | No |
-| endPoint | string |  | No |
-| path | string |  | No |
-| presign | boolean |  | No |
-| region | string |  | No |
-| secretKey | string |  | No |
-| urlPrefix | string |  | No |
-| urlSuffix | string |  | No |
-
-#### api_v1.StorageType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| api_v1.StorageType | string |  |  |
-
-#### api_v1.SystemSetting
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| description | string |  | No |
-| name | [api_v1.SystemSettingName](#api_v1systemsettingname) |  | No |
-| value | string | Value is a JSON string with basic value. | No |
-
-#### api_v1.SystemSettingName
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| api_v1.SystemSettingName | string |  |  |
-
-#### api_v1.SystemStatus
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| additionalScript | string | Additional script. | No |
-| additionalStyle | string | Additional style. | No |
-| allowSignUp | boolean | System settings Allow sign up. | No |
-| customizedProfile | [api_v1.CustomizedProfile](#api_v1customizedprofile) | Customized server profile, including server name and external url. | No |
-| dbSize | integer |  | No |
-| disablePasswordLogin | boolean | Disable password login. | No |
-| disablePublicMemos | boolean | Disable public memos. | No |
-| host | [api_v1.User](#api_v1user) |  | No |
-| localStoragePath | string | Local storage path. | No |
-| maxUploadSizeMiB | integer | Max upload size. | No |
-| memoDisplayWithUpdatedTs | boolean | Memo display with updated timestamp. | No |
-| profile | [profile.Profile](#profileprofile) |  | No |
-| storageServiceId | integer | Storage service ID. | No |
-
-#### api_v1.UpdateIdentityProviderRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [api_v1.IdentityProviderConfig](#api_v1identityproviderconfig) |  | No |
-| identifierFilter | string |  | No |
-| name | string |  | No |
-| type | [api_v1.IdentityProviderType](#api_v1identityprovidertype) |  | No |
-
-#### api_v1.UpdateResourceRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| filename | string |  | No |
-
-#### api_v1.UpdateStorageRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [api_v1.StorageConfig](#api_v1storageconfig) |  | No |
-| name | string |  | No |
-| type | [api_v1.StorageType](#api_v1storagetype) |  | No |
-
-#### api_v1.UpdateUserRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| avatarUrl | string |  | No |
-| email | string |  | No |
-| nickname | string |  | No |
-| password | string |  | No |
-| rowStatus | [api_v1.RowStatus](#api_v1rowstatus) |  | No |
-| username | string |  | No |
-
-#### api_v1.UpsertMemoOrganizerRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| pinned | boolean |  | No |
-
-#### api_v1.UpsertMemoRelationRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| relatedMemoId | integer |  | No |
-| type | [api_v1.MemoRelationType](#api_v1memorelationtype) |  | No |
-
-#### api_v1.UpsertSystemSettingRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| description | string |  | No |
-| name | [api_v1.SystemSettingName](#api_v1systemsettingname) |  | No |
-| value | string |  | No |
-
-#### api_v1.UpsertTagRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| name | string |  | No |
-
-#### api_v1.User
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| avatarUrl | string |  | No |
-| createdTs | integer |  | No |
-| email | string |  | No |
-| id | integer |  | No |
-| nickname | string |  | No |
-| role | [api_v1.Role](#api_v1role) |  | No |
-| rowStatus | [api_v1.RowStatus](#api_v1rowstatus) | Standard fields | No |
-| updatedTs | integer |  | No |
-| username | string | Domain specific fields | No |
-
-#### api_v1.Visibility
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| api_v1.Visibility | string |  |  |
-
-#### github_com_usememos_memos_api_v1.CreateIdentityProviderRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [github_com_usememos_memos_api_v1.IdentityProviderConfig](#github_com_usememos_memos_api_v1identityproviderconfig) |  | No |
-| identifierFilter | string |  | No |
-| name | string |  | No |
-| type | [github_com_usememos_memos_api_v1.IdentityProviderType](#github_com_usememos_memos_api_v1identityprovidertype) |  | No |
-
-#### github_com_usememos_memos_api_v1.CreateMemoRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| content | string |  | No |
-| createdTs | integer |  | No |
-| relationList | [ [github_com_usememos_memos_api_v1.UpsertMemoRelationRequest](#github_com_usememos_memos_api_v1upsertmemorelationrequest) ] |  | No |
-| resourceIdList | [ integer ] | Related fields | No |
-| visibility | [github_com_usememos_memos_api_v1.Visibility](#github_com_usememos_memos_api_v1visibility) | Domain specific fields | No |
-
-#### github_com_usememos_memos_api_v1.CreateResourceRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| externalLink | string |  | No |
-| filename | string |  | No |
-| type | string |  | No |
-
-#### github_com_usememos_memos_api_v1.CreateStorageRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [github_com_usememos_memos_api_v1.StorageConfig](#github_com_usememos_memos_api_v1storageconfig) |  | No |
-| name | string |  | No |
-| type | [github_com_usememos_memos_api_v1.StorageType](#github_com_usememos_memos_api_v1storagetype) |  | No |
-
-#### github_com_usememos_memos_api_v1.CreateUserRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| email | string |  | No |
-| nickname | string |  | No |
-| password | string |  | No |
-| role | [github_com_usememos_memos_api_v1.Role](#github_com_usememos_memos_api_v1role) |  | No |
-| username | string |  | No |
-
-#### github_com_usememos_memos_api_v1.CustomizedProfile
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| appearance | string | Appearance is the server default appearance. | No |
-| description | string | Description is the server description. | No |
-| locale | string | Locale is the server default locale. | No |
-| logoUrl | string | LogoURL is the url of logo image. | No |
-| name | string | Name is the server name, default is `memos` | No |
-
-#### github_com_usememos_memos_api_v1.DeleteTagRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| name | string |  | No |
-
-#### github_com_usememos_memos_api_v1.FieldMapping
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| displayName | string |  | No |
-| email | string |  | No |
-| identifier | string |  | No |
-
-#### github_com_usememos_memos_api_v1.IdentityProvider
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [github_com_usememos_memos_api_v1.IdentityProviderConfig](#github_com_usememos_memos_api_v1identityproviderconfig) |  | No |
-| id | integer |  | No |
-| identifierFilter | string |  | No |
-| name | string |  | No |
-| type | [github_com_usememos_memos_api_v1.IdentityProviderType](#github_com_usememos_memos_api_v1identityprovidertype) |  | No |
-
-#### github_com_usememos_memos_api_v1.IdentityProviderConfig
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| oauth2Config | [github_com_usememos_memos_api_v1.IdentityProviderOAuth2Config](#github_com_usememos_memos_api_v1identityprovideroauth2config) |  | No |
-
-#### github_com_usememos_memos_api_v1.IdentityProviderOAuth2Config
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| authUrl | string |  | No |
-| clientId | string |  | No |
-| clientSecret | string |  | No |
-| fieldMapping | [github_com_usememos_memos_api_v1.FieldMapping](#github_com_usememos_memos_api_v1fieldmapping) |  | No |
-| scopes | [ string ] |  | No |
-| tokenUrl | string |  | No |
-| userInfoUrl | string |  | No |
-
-#### github_com_usememos_memos_api_v1.IdentityProviderType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| github_com_usememos_memos_api_v1.IdentityProviderType | string |  |  |
-
-#### github_com_usememos_memos_api_v1.MemoRelationType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| github_com_usememos_memos_api_v1.MemoRelationType | string |  |  |
-
-#### github_com_usememos_memos_api_v1.PatchMemoRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| content | string | Domain specific fields | No |
-| createdTs | integer | Standard fields | No |
-| relationList | [ [github_com_usememos_memos_api_v1.UpsertMemoRelationRequest](#github_com_usememos_memos_api_v1upsertmemorelationrequest) ] |  | No |
-| resourceIdList | [ integer ] | Related fields | No |
-| rowStatus | [github_com_usememos_memos_api_v1.RowStatus](#github_com_usememos_memos_api_v1rowstatus) |  | No |
-| updatedTs | integer |  | No |
-| visibility | [github_com_usememos_memos_api_v1.Visibility](#github_com_usememos_memos_api_v1visibility) |  | No |
-
-#### github_com_usememos_memos_api_v1.Role
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| github_com_usememos_memos_api_v1.Role | string |  |  |
-
-#### github_com_usememos_memos_api_v1.RowStatus
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| github_com_usememos_memos_api_v1.RowStatus | string |  |  |
-
-#### github_com_usememos_memos_api_v1.SSOSignIn
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| code | string |  | No |
-| identityProviderId | integer |  | No |
-| redirectUri | string |  | No |
-
-#### github_com_usememos_memos_api_v1.SignIn
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| password | string |  | No |
-| remember | boolean |  | No |
-| username | string |  | No |
-
-#### github_com_usememos_memos_api_v1.SignUp
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| password | string |  | No |
-| username | string |  | No |
-
-#### github_com_usememos_memos_api_v1.StorageConfig
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| s3Config | [github_com_usememos_memos_api_v1.StorageS3Config](#github_com_usememos_memos_api_v1storages3config) |  | No |
-
-#### github_com_usememos_memos_api_v1.StorageS3Config
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| accessKey | string |  | No |
-| bucket | string |  | No |
-| endPoint | string |  | No |
-| path | string |  | No |
-| presign | boolean |  | No |
-| region | string |  | No |
-| secretKey | string |  | No |
-| urlPrefix | string |  | No |
-| urlSuffix | string |  | No |
-
-#### github_com_usememos_memos_api_v1.StorageType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| github_com_usememos_memos_api_v1.StorageType | string |  |  |
-
-#### github_com_usememos_memos_api_v1.SystemSetting
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| description | string |  | No |
-| name | [github_com_usememos_memos_api_v1.SystemSettingName](#github_com_usememos_memos_api_v1systemsettingname) |  | No |
-| value | string | Value is a JSON string with basic value. | No |
-
-#### github_com_usememos_memos_api_v1.SystemSettingName
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| github_com_usememos_memos_api_v1.SystemSettingName | string |  |  |
-
-#### github_com_usememos_memos_api_v1.SystemStatus
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| additionalScript | string | Additional script. | No |
-| additionalStyle | string | Additional style. | No |
-| allowSignUp | boolean | System settings Allow sign up. | No |
-| customizedProfile | [github_com_usememos_memos_api_v1.CustomizedProfile](#github_com_usememos_memos_api_v1customizedprofile) | Customized server profile, including server name and external url. | No |
-| dbSize | integer |  | No |
-| disablePasswordLogin | boolean | Disable password login. | No |
-| disablePublicMemos | boolean | Disable public memos. | No |
-| host | [github_com_usememos_memos_api_v1.User](#github_com_usememos_memos_api_v1user) |  | No |
-| localStoragePath | string | Local storage path. | No |
-| maxUploadSizeMiB | integer | Max upload size. | No |
-| memoDisplayWithUpdatedTs | boolean | Memo display with updated timestamp. | No |
-| profile | [profile.Profile](#profileprofile) |  | No |
-| storageServiceId | integer | Storage service ID. | No |
-
-#### github_com_usememos_memos_api_v1.UpdateIdentityProviderRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [github_com_usememos_memos_api_v1.IdentityProviderConfig](#github_com_usememos_memos_api_v1identityproviderconfig) |  | No |
-| identifierFilter | string |  | No |
-| name | string |  | No |
-| type | [github_com_usememos_memos_api_v1.IdentityProviderType](#github_com_usememos_memos_api_v1identityprovidertype) |  | No |
-
-#### github_com_usememos_memos_api_v1.UpdateResourceRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| filename | string |  | No |
-
-#### github_com_usememos_memos_api_v1.UpdateStorageRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [github_com_usememos_memos_api_v1.StorageConfig](#github_com_usememos_memos_api_v1storageconfig) |  | No |
-| name | string |  | No |
-| type | [github_com_usememos_memos_api_v1.StorageType](#github_com_usememos_memos_api_v1storagetype) |  | No |
-
-#### github_com_usememos_memos_api_v1.UpdateUserRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| avatarUrl | string |  | No |
-| email | string |  | No |
-| nickname | string |  | No |
-| password | string |  | No |
-| rowStatus | [github_com_usememos_memos_api_v1.RowStatus](#github_com_usememos_memos_api_v1rowstatus) |  | No |
-| username | string |  | No |
-
-#### github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| pinned | boolean |  | No |
-
-#### github_com_usememos_memos_api_v1.UpsertMemoRelationRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| relatedMemoId | integer |  | No |
-| type | [github_com_usememos_memos_api_v1.MemoRelationType](#github_com_usememos_memos_api_v1memorelationtype) |  | No |
-
-#### github_com_usememos_memos_api_v1.UpsertSystemSettingRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| description | string |  | No |
-| name | [github_com_usememos_memos_api_v1.SystemSettingName](#github_com_usememos_memos_api_v1systemsettingname) |  | No |
-| value | string |  | No |
-
-#### github_com_usememos_memos_api_v1.UpsertTagRequest
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| name | string |  | No |
-
-#### github_com_usememos_memos_api_v1.User
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| avatarUrl | string |  | No |
-| createdTs | integer |  | No |
-| email | string |  | No |
-| id | integer |  | No |
-| nickname | string |  | No |
-| role | [github_com_usememos_memos_api_v1.Role](#github_com_usememos_memos_api_v1role) |  | No |
-| rowStatus | [github_com_usememos_memos_api_v1.RowStatus](#github_com_usememos_memos_api_v1rowstatus) | Standard fields | No |
-| updatedTs | integer |  | No |
-| username | string | Domain specific fields | No |
-
-#### github_com_usememos_memos_api_v1.Visibility
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| github_com_usememos_memos_api_v1.Visibility | string |  |  |
-
-#### profile.Profile
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| mode | string | Mode can be "prod" or "dev" or "demo" | No |
-| version | string | Version is the current version of server | No |
-
-#### store.FieldMapping
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| displayName | string |  | No |
-| email | string |  | No |
-| identifier | string |  | No |
-
-#### store.IdentityProvider
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | [store.IdentityProviderConfig](#storeidentityproviderconfig) |  | No |
-| id | integer |  | No |
-| identifierFilter | string |  | No |
-| name | string |  | No |
-| type | [store.IdentityProviderType](#storeidentityprovidertype) |  | No |
-
-#### store.IdentityProviderConfig
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| oauth2Config | [store.IdentityProviderOAuth2Config](#storeidentityprovideroauth2config) |  | No |
-
-#### store.IdentityProviderOAuth2Config
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| authUrl | string |  | No |
-| clientId | string |  | No |
-| clientSecret | string |  | No |
-| fieldMapping | [store.FieldMapping](#storefieldmapping) |  | No |
-| scopes | [ string ] |  | No |
-| tokenUrl | string |  | No |
-| userInfoUrl | string |  | No |
-
-#### store.IdentityProviderType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| store.IdentityProviderType | string |  |  |
-
-#### store.Memo
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| content | string | Domain specific fields | No |
-| createdTs | integer |  | No |
-| creatorID | integer |  | No |
-| id | integer |  | No |
-| parentID | integer |  | No |
-| pinned | boolean | Composed fields | No |
-| resourceName | string |  | No |
-| rowStatus | [store.RowStatus](#storerowstatus) | Standard fields | No |
-| updatedTs | integer |  | No |
-| visibility | [store.Visibility](#storevisibility) |  | No |
-
-#### store.MemoRelation
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| memoID | integer |  | No |
-| relatedMemoID | integer |  | No |
-| type | [store.MemoRelationType](#storememorelationtype) |  | No |
-
-#### store.MemoRelationType
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| store.MemoRelationType | string |  |  |
-
-#### store.Resource
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| blob | [ integer ] |  | No |
-| createdTs | integer |  | No |
-| creatorID | integer | Standard fields | No |
-| externalLink | string |  | No |
-| filename | string | Domain specific fields | No |
-| id | integer |  | No |
-| internalPath | string |  | No |
-| memoID | integer |  | No |
-| resourceName | string |  | No |
-| size | integer |  | No |
-| type | string |  | No |
-| updatedTs | integer |  | No |
-
-#### store.Role
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| store.Role | string |  |  |
-
-#### store.RowStatus
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| store.RowStatus | string |  |  |
-
-#### store.Storage
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| config | string |  | No |
-| id | integer |  | No |
-| name | string |  | No |
-| type | string |  | No |
-
-#### store.User
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| avatarURL | string |  | No |
-| createdTs | integer |  | No |
-| email | string |  | No |
-| id | integer |  | No |
-| nickname | string |  | No |
-| passwordHash | string |  | No |
-| role | [store.Role](#storerole) |  | No |
-| rowStatus | [store.RowStatus](#storerowstatus) | Standard fields | No |
-| updatedTs | integer |  | No |
-| username | string | Domain specific fields | No |
-
-#### store.Visibility
-
-| Name | Type | Description | Required |
-| ---- | ---- | ----------- | -------- |
-| store.Visibility | string |  |  |

programming_language: unknown
filename: swagger.yaml
change_type: ModificationType.DELETE
diff: @@ -1,2278 +0,0 @@
-basePath: /
-definitions:
-  api_v1.CreateIdentityProviderRequest:
-    properties:
-      config:
-        $ref: '#/definitions/api_v1.IdentityProviderConfig'
-      identifierFilter:
-        type: string
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/api_v1.IdentityProviderType'
-    type: object
-  api_v1.CreateMemoRequest:
-    properties:
-      content:
-        type: string
-      createdTs:
-        type: integer
-      relationList:
-        items:
-          $ref: '#/definitions/api_v1.UpsertMemoRelationRequest'
-        type: array
-      resourceIdList:
-        description: Related fields
-        items:
-          type: integer
-        type: array
-      visibility:
-        allOf:
-        - $ref: '#/definitions/api_v1.Visibility'
-        description: Domain specific fields
-    type: object
-  api_v1.CreateResourceRequest:
-    properties:
-      externalLink:
-        type: string
-      filename:
-        type: string
-      type:
-        type: string
-    type: object
-  api_v1.CreateStorageRequest:
-    properties:
-      config:
-        $ref: '#/definitions/api_v1.StorageConfig'
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/api_v1.StorageType'
-    type: object
-  api_v1.CreateUserRequest:
-    properties:
-      email:
-        type: string
-      nickname:
-        type: string
-      password:
-        type: string
-      role:
-        $ref: '#/definitions/api_v1.Role'
-      username:
-        type: string
-    type: object
-  api_v1.CustomizedProfile:
-    properties:
-      appearance:
-        description: Appearance is the server default appearance.
-        type: string
-      description:
-        description: Description is the server description.
-        type: string
-      locale:
-        description: Locale is the server default locale.
-        type: string
-      logoUrl:
-        description: LogoURL is the url of logo image.
-        type: string
-      name:
-        description: Name is the server name, default is `memos`
-        type: string
-    type: object
-  api_v1.DeleteTagRequest:
-    properties:
-      name:
-        type: string
-    type: object
-  api_v1.FieldMapping:
-    properties:
-      displayName:
-        type: string
-      email:
-        type: string
-      identifier:
-        type: string
-    type: object
-  api_v1.IdentityProvider:
-    properties:
-      config:
-        $ref: '#/definitions/api_v1.IdentityProviderConfig'
-      id:
-        type: integer
-      identifierFilter:
-        type: string
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/api_v1.IdentityProviderType'
-    type: object
-  api_v1.IdentityProviderConfig:
-    properties:
-      oauth2Config:
-        $ref: '#/definitions/api_v1.IdentityProviderOAuth2Config'
-    type: object
-  api_v1.IdentityProviderOAuth2Config:
-    properties:
-      authUrl:
-        type: string
-      clientId:
-        type: string
-      clientSecret:
-        type: string
-      fieldMapping:
-        $ref: '#/definitions/api_v1.FieldMapping'
-      scopes:
-        items:
-          type: string
-        type: array
-      tokenUrl:
-        type: string
-      userInfoUrl:
-        type: string
-    type: object
-  api_v1.IdentityProviderType:
-    enum:
-    - OAUTH2
-    type: string
-    x-enum-varnames:
-    - IdentityProviderOAuth2Type
-  api_v1.MemoRelationType:
-    enum:
-    - REFERENCE
-    - COMMENT
-    type: string
-    x-enum-varnames:
-    - MemoRelationReference
-    - MemoRelationComment
-  api_v1.PatchMemoRequest:
-    properties:
-      content:
-        description: Domain specific fields
-        type: string
-      createdTs:
-        description: Standard fields
-        type: integer
-      relationList:
-        items:
-          $ref: '#/definitions/api_v1.UpsertMemoRelationRequest'
-        type: array
-      resourceIdList:
-        description: Related fields
-        items:
-          type: integer
-        type: array
-      rowStatus:
-        $ref: '#/definitions/api_v1.RowStatus'
-      updatedTs:
-        type: integer
-      visibility:
-        $ref: '#/definitions/api_v1.Visibility'
-    type: object
-  api_v1.Role:
-    enum:
-    - HOST
-    - ADMIN
-    - USER
-    type: string
-    x-enum-varnames:
-    - RoleHost
-    - RoleAdmin
-    - RoleUser
-  api_v1.RowStatus:
-    enum:
-    - NORMAL
-    - ARCHIVED
-    type: string
-    x-enum-varnames:
-    - Normal
-    - Archived
-  api_v1.SSOSignIn:
-    properties:
-      code:
-        type: string
-      identityProviderId:
-        type: integer
-      redirectUri:
-        type: string
-    type: object
-  api_v1.SignIn:
-    properties:
-      password:
-        type: string
-      remember:
-        type: boolean
-      username:
-        type: string
-    type: object
-  api_v1.SignUp:
-    properties:
-      password:
-        type: string
-      username:
-        type: string
-    type: object
-  api_v1.StorageConfig:
-    properties:
-      s3Config:
-        $ref: '#/definitions/api_v1.StorageS3Config'
-    type: object
-  api_v1.StorageS3Config:
-    properties:
-      accessKey:
-        type: string
-      bucket:
-        type: string
-      endPoint:
-        type: string
-      path:
-        type: string
-      presign:
-        type: boolean
-      region:
-        type: string
-      secretKey:
-        type: string
-      urlPrefix:
-        type: string
-      urlSuffix:
-        type: string
-    type: object
-  api_v1.StorageType:
-    enum:
-    - S3
-    type: string
-    x-enum-varnames:
-    - StorageS3
-  api_v1.SystemSetting:
-    properties:
-      description:
-        type: string
-      name:
-        $ref: '#/definitions/api_v1.SystemSettingName'
-      value:
-        description: Value is a JSON string with basic value.
-        type: string
-    type: object
-  api_v1.SystemSettingName:
-    enum:
-    - server-id
-    - secret-session
-    - disable-public-memos
-    - max-upload-size-mib
-    - customized-profile
-    - storage-service-id
-    - local-storage-path
-    - telegram-bot-token
-    - memo-display-with-updated-ts
-    type: string
-    x-enum-varnames:
-    - SystemSettingServerIDName
-    - SystemSettingSecretSessionName
-    - SystemSettingDisablePublicMemosName
-    - SystemSettingMaxUploadSizeMiBName
-    - SystemSettingCustomizedProfileName
-    - SystemSettingStorageServiceIDName
-    - SystemSettingLocalStoragePathName
-    - SystemSettingTelegramBotTokenName
-    - SystemSettingMemoDisplayWithUpdatedTsName
-  api_v1.SystemStatus:
-    properties:
-      additionalScript:
-        description: Additional script.
-        type: string
-      additionalStyle:
-        description: Additional style.
-        type: string
-      allowSignUp:
-        description: |-
-          System settings
-          Allow sign up.
-        type: boolean
-      customizedProfile:
-        allOf:
-        - $ref: '#/definitions/api_v1.CustomizedProfile'
-        description: Customized server profile, including server name and external
-          url.
-      dbSize:
-        type: integer
-      disablePasswordLogin:
-        description: Disable password login.
-        type: boolean
-      disablePublicMemos:
-        description: Disable public memos.
-        type: boolean
-      host:
-        $ref: '#/definitions/api_v1.User'
-      localStoragePath:
-        description: Local storage path.
-        type: string
-      maxUploadSizeMiB:
-        description: Max upload size.
-        type: integer
-      memoDisplayWithUpdatedTs:
-        description: Memo display with updated timestamp.
-        type: boolean
-      profile:
-        $ref: '#/definitions/profile.Profile'
-      storageServiceId:
-        description: Storage service ID.
-        type: integer
-    type: object
-  api_v1.UpdateIdentityProviderRequest:
-    properties:
-      config:
-        $ref: '#/definitions/api_v1.IdentityProviderConfig'
-      identifierFilter:
-        type: string
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/api_v1.IdentityProviderType'
-    type: object
-  api_v1.UpdateResourceRequest:
-    properties:
-      filename:
-        type: string
-    type: object
-  api_v1.UpdateStorageRequest:
-    properties:
-      config:
-        $ref: '#/definitions/api_v1.StorageConfig'
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/api_v1.StorageType'
-    type: object
-  api_v1.UpdateUserRequest:
-    properties:
-      avatarUrl:
-        type: string
-      email:
-        type: string
-      nickname:
-        type: string
-      password:
-        type: string
-      rowStatus:
-        $ref: '#/definitions/api_v1.RowStatus'
-      username:
-        type: string
-    type: object
-  api_v1.UpsertMemoOrganizerRequest:
-    properties:
-      pinned:
-        type: boolean
-    type: object
-  api_v1.UpsertMemoRelationRequest:
-    properties:
-      relatedMemoId:
-        type: integer
-      type:
-        $ref: '#/definitions/api_v1.MemoRelationType'
-    type: object
-  api_v1.UpsertSystemSettingRequest:
-    properties:
-      description:
-        type: string
-      name:
-        $ref: '#/definitions/api_v1.SystemSettingName'
-      value:
-        type: string
-    type: object
-  api_v1.UpsertTagRequest:
-    properties:
-      name:
-        type: string
-    type: object
-  api_v1.User:
-    properties:
-      avatarUrl:
-        type: string
-      createdTs:
-        type: integer
-      email:
-        type: string
-      id:
-        type: integer
-      nickname:
-        type: string
-      role:
-        $ref: '#/definitions/api_v1.Role'
-      rowStatus:
-        allOf:
-        - $ref: '#/definitions/api_v1.RowStatus'
-        description: Standard fields
-      updatedTs:
-        type: integer
-      username:
-        description: Domain specific fields
-        type: string
-    type: object
-  api_v1.Visibility:
-    enum:
-    - PUBLIC
-    - PROTECTED
-    - PRIVATE
-    type: string
-    x-enum-varnames:
-    - Public
-    - Protected
-    - Private
-  github_com_usememos_memos_api_v1.CreateIdentityProviderRequest:
-    properties:
-      config:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProviderConfig'
-      identifierFilter:
-        type: string
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProviderType'
-    type: object
-  github_com_usememos_memos_api_v1.CreateMemoRequest:
-    properties:
-      content:
-        type: string
-      createdTs:
-        type: integer
-      relationList:
-        items:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest'
-        type: array
-      resourceIdList:
-        description: Related fields
-        items:
-          type: integer
-        type: array
-      visibility:
-        allOf:
-        - $ref: '#/definitions/github_com_usememos_memos_api_v1.Visibility'
-        description: Domain specific fields
-    type: object
-  github_com_usememos_memos_api_v1.CreateResourceRequest:
-    properties:
-      externalLink:
-        type: string
-      filename:
-        type: string
-      type:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.CreateStorageRequest:
-    properties:
-      config:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.StorageConfig'
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.StorageType'
-    type: object
-  github_com_usememos_memos_api_v1.CreateUserRequest:
-    properties:
-      email:
-        type: string
-      nickname:
-        type: string
-      password:
-        type: string
-      role:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.Role'
-      username:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.CustomizedProfile:
-    properties:
-      appearance:
-        description: Appearance is the server default appearance.
-        type: string
-      description:
-        description: Description is the server description.
-        type: string
-      locale:
-        description: Locale is the server default locale.
-        type: string
-      logoUrl:
-        description: LogoURL is the url of logo image.
-        type: string
-      name:
-        description: Name is the server name, default is `memos`
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.DeleteTagRequest:
-    properties:
-      name:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.FieldMapping:
-    properties:
-      displayName:
-        type: string
-      email:
-        type: string
-      identifier:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.IdentityProvider:
-    properties:
-      config:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProviderConfig'
-      id:
-        type: integer
-      identifierFilter:
-        type: string
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProviderType'
-    type: object
-  github_com_usememos_memos_api_v1.IdentityProviderConfig:
-    properties:
-      oauth2Config:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProviderOAuth2Config'
-    type: object
-  github_com_usememos_memos_api_v1.IdentityProviderOAuth2Config:
-    properties:
-      authUrl:
-        type: string
-      clientId:
-        type: string
-      clientSecret:
-        type: string
-      fieldMapping:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.FieldMapping'
-      scopes:
-        items:
-          type: string
-        type: array
-      tokenUrl:
-        type: string
-      userInfoUrl:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.IdentityProviderType:
-    enum:
-    - OAUTH2
-    type: string
-    x-enum-varnames:
-    - IdentityProviderOAuth2Type
-  github_com_usememos_memos_api_v1.MemoRelationType:
-    enum:
-    - REFERENCE
-    - COMMENT
-    type: string
-    x-enum-varnames:
-    - MemoRelationReference
-    - MemoRelationComment
-  github_com_usememos_memos_api_v1.PatchMemoRequest:
-    properties:
-      content:
-        description: Domain specific fields
-        type: string
-      createdTs:
-        description: Standard fields
-        type: integer
-      relationList:
-        items:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest'
-        type: array
-      resourceIdList:
-        description: Related fields
-        items:
-          type: integer
-        type: array
-      rowStatus:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.RowStatus'
-      updatedTs:
-        type: integer
-      visibility:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.Visibility'
-    type: object
-  github_com_usememos_memos_api_v1.Role:
-    enum:
-    - HOST
-    - ADMIN
-    - USER
-    type: string
-    x-enum-varnames:
-    - RoleHost
-    - RoleAdmin
-    - RoleUser
-  github_com_usememos_memos_api_v1.RowStatus:
-    enum:
-    - NORMAL
-    - ARCHIVED
-    type: string
-    x-enum-varnames:
-    - Normal
-    - Archived
-  github_com_usememos_memos_api_v1.SSOSignIn:
-    properties:
-      code:
-        type: string
-      identityProviderId:
-        type: integer
-      redirectUri:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.SignIn:
-    properties:
-      password:
-        type: string
-      remember:
-        type: boolean
-      username:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.SignUp:
-    properties:
-      password:
-        type: string
-      username:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.StorageConfig:
-    properties:
-      s3Config:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.StorageS3Config'
-    type: object
-  github_com_usememos_memos_api_v1.StorageS3Config:
-    properties:
-      accessKey:
-        type: string
-      bucket:
-        type: string
-      endPoint:
-        type: string
-      path:
-        type: string
-      presign:
-        type: boolean
-      region:
-        type: string
-      secretKey:
-        type: string
-      urlPrefix:
-        type: string
-      urlSuffix:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.StorageType:
-    enum:
-    - S3
-    type: string
-    x-enum-varnames:
-    - StorageS3
-  github_com_usememos_memos_api_v1.SystemSetting:
-    properties:
-      description:
-        type: string
-      name:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.SystemSettingName'
-      value:
-        description: Value is a JSON string with basic value.
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.SystemSettingName:
-    enum:
-    - server-id
-    - secret-session
-    - disable-public-memos
-    - max-upload-size-mib
-    - customized-profile
-    - storage-service-id
-    - local-storage-path
-    - telegram-bot-token
-    - memo-display-with-updated-ts
-    type: string
-    x-enum-varnames:
-    - SystemSettingServerIDName
-    - SystemSettingSecretSessionName
-    - SystemSettingDisablePublicMemosName
-    - SystemSettingMaxUploadSizeMiBName
-    - SystemSettingCustomizedProfileName
-    - SystemSettingStorageServiceIDName
-    - SystemSettingLocalStoragePathName
-    - SystemSettingTelegramBotTokenName
-    - SystemSettingMemoDisplayWithUpdatedTsName
-  github_com_usememos_memos_api_v1.SystemStatus:
-    properties:
-      additionalScript:
-        description: Additional script.
-        type: string
-      additionalStyle:
-        description: Additional style.
-        type: string
-      allowSignUp:
-        description: |-
-          System settings
-          Allow sign up.
-        type: boolean
-      customizedProfile:
-        allOf:
-        - $ref: '#/definitions/github_com_usememos_memos_api_v1.CustomizedProfile'
-        description: Customized server profile, including server name and external
-          url.
-      dbSize:
-        type: integer
-      disablePasswordLogin:
-        description: Disable password login.
-        type: boolean
-      disablePublicMemos:
-        description: Disable public memos.
-        type: boolean
-      host:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.User'
-      localStoragePath:
-        description: Local storage path.
-        type: string
-      maxUploadSizeMiB:
-        description: Max upload size.
-        type: integer
-      memoDisplayWithUpdatedTs:
-        description: Memo display with updated timestamp.
-        type: boolean
-      profile:
-        $ref: '#/definitions/profile.Profile'
-      storageServiceId:
-        description: Storage service ID.
-        type: integer
-    type: object
-  github_com_usememos_memos_api_v1.UpdateIdentityProviderRequest:
-    properties:
-      config:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProviderConfig'
-      identifierFilter:
-        type: string
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.IdentityProviderType'
-    type: object
-  github_com_usememos_memos_api_v1.UpdateResourceRequest:
-    properties:
-      filename:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.UpdateStorageRequest:
-    properties:
-      config:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.StorageConfig'
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.StorageType'
-    type: object
-  github_com_usememos_memos_api_v1.UpdateUserRequest:
-    properties:
-      avatarUrl:
-        type: string
-      email:
-        type: string
-      nickname:
-        type: string
-      password:
-        type: string
-      rowStatus:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.RowStatus'
-      username:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest:
-    properties:
-      pinned:
-        type: boolean
-    type: object
-  github_com_usememos_memos_api_v1.UpsertMemoRelationRequest:
-    properties:
-      relatedMemoId:
-        type: integer
-      type:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.MemoRelationType'
-    type: object
-  github_com_usememos_memos_api_v1.UpsertSystemSettingRequest:
-    properties:
-      description:
-        type: string
-      name:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.SystemSettingName'
-      value:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.UpsertTagRequest:
-    properties:
-      name:
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.User:
-    properties:
-      avatarUrl:
-        type: string
-      createdTs:
-        type: integer
-      email:
-        type: string
-      id:
-        type: integer
-      nickname:
-        type: string
-      role:
-        $ref: '#/definitions/github_com_usememos_memos_api_v1.Role'
-      rowStatus:
-        allOf:
-        - $ref: '#/definitions/github_com_usememos_memos_api_v1.RowStatus'
-        description: Standard fields
-      updatedTs:
-        type: integer
-      username:
-        description: Domain specific fields
-        type: string
-    type: object
-  github_com_usememos_memos_api_v1.Visibility:
-    enum:
-    - PUBLIC
-    - PROTECTED
-    - PRIVATE
-    type: string
-    x-enum-varnames:
-    - Public
-    - Protected
-    - Private
-  profile.Profile:
-    properties:
-      mode:
-        description: Mode can be "prod" or "dev" or "demo"
-        type: string
-      version:
-        description: Version is the current version of server
-        type: string
-    type: object
-  store.FieldMapping:
-    properties:
-      displayName:
-        type: string
-      email:
-        type: string
-      identifier:
-        type: string
-    type: object
-  store.IdentityProvider:
-    properties:
-      config:
-        $ref: '#/definitions/store.IdentityProviderConfig'
-      id:
-        type: integer
-      identifierFilter:
-        type: string
-      name:
-        type: string
-      type:
-        $ref: '#/definitions/store.IdentityProviderType'
-    type: object
-  store.IdentityProviderConfig:
-    properties:
-      oauth2Config:
-        $ref: '#/definitions/store.IdentityProviderOAuth2Config'
-    type: object
-  store.IdentityProviderOAuth2Config:
-    properties:
-      authUrl:
-        type: string
-      clientId:
-        type: string
-      clientSecret:
-        type: string
-      fieldMapping:
-        $ref: '#/definitions/store.FieldMapping'
-      scopes:
-        items:
-          type: string
-        type: array
-      tokenUrl:
-        type: string
-      userInfoUrl:
-        type: string
-    type: object
-  store.IdentityProviderType:
-    enum:
-    - OAUTH2
-    type: string
-    x-enum-varnames:
-    - IdentityProviderOAuth2Type
-  store.Memo:
-    properties:
-      content:
-        description: Domain specific fields
-        type: string
-      createdTs:
-        type: integer
-      creatorID:
-        type: integer
-      id:
-        type: integer
-      parentID:
-        type: integer
-      pinned:
-        description: Composed fields
-        type: boolean
-      resourceName:
-        type: string
-      rowStatus:
-        allOf:
-        - $ref: '#/definitions/store.RowStatus'
-        description: Standard fields
-      updatedTs:
-        type: integer
-      visibility:
-        $ref: '#/definitions/store.Visibility'
-    type: object
-  store.MemoRelation:
-    properties:
-      memoID:
-        type: integer
-      relatedMemoID:
-        type: integer
-      type:
-        $ref: '#/definitions/store.MemoRelationType'
-    type: object
-  store.MemoRelationType:
-    enum:
-    - REFERENCE
-    - COMMENT
-    type: string
-    x-enum-varnames:
-    - MemoRelationReference
-    - MemoRelationComment
-  store.Resource:
-    properties:
-      blob:
-        items:
-          type: integer
-        type: array
-      createdTs:
-        type: integer
-      creatorID:
-        description: Standard fields
-        type: integer
-      externalLink:
-        type: string
-      filename:
-        description: Domain specific fields
-        type: string
-      id:
-        type: integer
-      internalPath:
-        type: string
-      memoID:
-        type: integer
-      resourceName:
-        type: string
-      size:
-        type: integer
-      type:
-        type: string
-      updatedTs:
-        type: integer
-    type: object
-  store.Role:
-    enum:
-    - HOST
-    - ADMIN
-    - USER
-    type: string
-    x-enum-varnames:
-    - RoleHost
-    - RoleAdmin
-    - RoleUser
-  store.RowStatus:
-    enum:
-    - NORMAL
-    - ARCHIVED
-    type: string
-    x-enum-varnames:
-    - Normal
-    - Archived
-  store.Storage:
-    properties:
-      config:
-        type: string
-      id:
-        type: integer
-      name:
-        type: string
-      type:
-        type: string
-    type: object
-  store.User:
-    properties:
-      avatarURL:
-        type: string
-      createdTs:
-        type: integer
-      email:
-        type: string
-      id:
-        type: integer
-      nickname:
-        type: string
-      passwordHash:
-        type: string
-      role:
-        $ref: '#/definitions/store.Role'
-      rowStatus:
-        allOf:
-        - $ref: '#/definitions/store.RowStatus'
-        description: Standard fields
-      updatedTs:
-        type: integer
-      username:
-        description: Domain specific fields
-        type: string
-    type: object
-  store.Visibility:
-    enum:
-    - PUBLIC
-    - PROTECTED
-    - PRIVATE
-    type: string
-    x-enum-varnames:
-    - Public
-    - Protected
-    - Private
-externalDocs:
-  description: Find out more about Memos.
-  url: https://usememos.com/
-info:
-  contact:
-    name: API Support
-    url: https://github.com/orgs/usememos/discussions
-  description: A privacy-first, lightweight note-taking service.
-  license:
-    name: MIT License
-    url: https://github.com/usememos/memos/blob/main/LICENSE
-  title: memos API
-  version: "1.0"
-paths:
-  /api/v1/auth/signin:
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Sign-in object
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignIn'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: User information
-          schema:
-            $ref: '#/definitions/store.User'
-        "400":
-          description: Malformatted signin request
-        "401":
-          description: Password login is deactivated | Incorrect login credentials,
-            please try again
-        "403":
-          description: User has been archived with username %s
-        "500":
-          description: Failed to find system setting | Failed to unmarshal system
-            setting | Incorrect login credentials, please try again | Failed to generate
-            tokens | Failed to create activity
-      summary: Sign-in to memos.
-      tags:
-      - auth
-  /api/v1/auth/signin/sso:
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: SSO sign-in object
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SSOSignIn'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: User information
-          schema:
-            $ref: '#/definitions/store.User'
-        "400":
-          description: Malformatted signin request
-        "401":
-          description: Access denied, identifier does not match the filter.
-        "403":
-          description: User has been archived with username {username}
-        "404":
-          description: Identity provider not found
-        "500":
-          description: Failed to find identity provider | Failed to create identity
-            provider instance | Failed to exchange token | Failed to get user info
-            | Failed to compile identifier filter | Incorrect login credentials, please
-            try again | Failed to generate random password | Failed to generate password
-            hash | Failed to create user | Failed to generate tokens | Failed to create
-            activity
-      summary: Sign-in to memos using SSO.
-      tags:
-      - auth
-  /api/v1/auth/signout:
-    post:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Sign-out success
-          schema:
-            type: boolean
-      summary: Sign-out from memos.
-      tags:
-      - auth
-  /api/v1/auth/signup:
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Sign-up object
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.SignUp'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: User information
-          schema:
-            $ref: '#/definitions/store.User'
-        "400":
-          description: Malformatted signup request | Failed to find users
-        "401":
-          description: signup is disabled
-        "403":
-          description: Forbidden
-        "404":
-          description: Not found
-        "500":
-          description: Failed to find system setting | Failed to unmarshal system
-            setting allow signup | Failed to generate password hash | Failed to create
-            user | Failed to generate tokens | Failed to create activity
-      summary: Sign-up to memos.
-      tags:
-      - auth
-  /api/v1/idp:
-    get:
-      description: '*clientSecret is only available for host user'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: List of available identity providers
-          schema:
-            items:
-              $ref: '#/definitions/api_v1.IdentityProvider'
-            type: array
-        "500":
-          description: Failed to find identity provider list | Failed to find user
-      summary: Get a list of identity providers
-      tags:
-      - idp
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Identity provider information
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/api_v1.CreateIdentityProviderRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Identity provider information
-          schema:
-            $ref: '#/definitions/store.IdentityProvider'
-        "400":
-          description: Malformatted post identity provider request
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to create identity provider
-      summary: Create Identity Provider
-      tags:
-      - idp
-  /api/v1/idp/{idpId}:
-    delete:
-      consumes:
-      - application/json
-      parameters:
-      - description: Identity Provider ID
-        in: path
-        name: idpId
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Identity Provider deleted
-          schema:
-            type: boolean
-        "400":
-          description: 'ID is not a number: %s | Malformatted patch identity provider
-            request'
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to patch identity provider
-      summary: Delete an identity provider by ID
-      tags:
-      - idp
-    get:
-      consumes:
-      - application/json
-      parameters:
-      - description: Identity provider ID
-        in: path
-        name: idpId
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Requested identity provider
-          schema:
-            $ref: '#/definitions/store.IdentityProvider'
-        "400":
-          description: 'ID is not a number: %s'
-        "401":
-          description: Missing user in session | Unauthorized
-        "404":
-          description: Identity provider not found
-        "500":
-          description: Failed to find identity provider list | Failed to find user
-      summary: Get an identity provider by ID
-      tags:
-      - idp
-    patch:
-      consumes:
-      - application/json
-      parameters:
-      - description: Identity Provider ID
-        in: path
-        name: idpId
-        required: true
-        type: integer
-      - description: Patched identity provider information
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/api_v1.UpdateIdentityProviderRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Patched identity provider
-          schema:
-            $ref: '#/definitions/store.IdentityProvider'
-        "400":
-          description: 'ID is not a number: %s | Malformatted patch identity provider
-            request'
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to patch identity provider
-      summary: Update an identity provider by ID
-      tags:
-      - idp
-  /api/v1/memo:
-    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/json
-      responses:
-        "200":
-          description: Memo list
-          schema:
-            items:
-              $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 compose memo response
-      summary: Get a list of memos matching optional filters
-      tags:
-      - memo
-    post:
-      consumes:
-      - application/json
-      description: |-
-        Visibility can be PUBLIC, PROTECTED or PRIVATE
-        *You should omit fields to use their default values
-      parameters:
-      - description: Request object.
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateMemoRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Stored memo
-          schema:
-            $ref: '#/definitions/store.Memo'
-        "400":
-          description: Malformatted post memo request | Content size overflow, up
-            to 1MB
-        "401":
-          description: Missing user in session
-        "404":
-          description: 'User not found | Memo not found: %d'
-        "500":
-          description: Failed to find user setting | Failed to unmarshal user setting
-            value | Failed to find system setting | Failed to unmarshal system setting
-            | Failed to find user | Failed to create memo | Failed to create activity
-            | Failed to upsert memo resource | Failed to upsert memo relation | Failed
-            to compose memo | Failed to compose memo response
-      summary: Create a memo
-      tags:
-      - memo
-  /api/v1/memo/{memoId}:
-    delete:
-      parameters:
-      - description: Memo ID to delete
-        in: path
-        name: memoId
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo deleted
-          schema:
-            type: boolean
-        "400":
-          description: 'ID is not a number: %s'
-        "401":
-          description: Missing user in session | Unauthorized
-        "404":
-          description: 'Memo not found: %d'
-        "500":
-          description: 'Failed to find memo | Failed to delete memo ID: %v'
-      summary: Delete memo by ID
-      tags:
-      - memo
-    get:
-      parameters:
-      - description: Memo ID
-        in: path
-        name: memoId
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo list
-          schema:
-            items:
-              $ref: '#/definitions/store.Memo'
-            type: array
-        "400":
-          description: 'ID is not a number: %s'
-        "401":
-          description: Missing user in session
-        "403":
-          description: this memo is private only | this memo is protected, missing
-            user in session
-        "404":
-          description: 'Memo not found: %d'
-        "500":
-          description: 'Failed to find memo by ID: %v | Failed to compose memo response'
-      summary: Get memo by ID
-      tags:
-      - memo
-    patch:
-      consumes:
-      - application/json
-      description: |-
-        Visibility can be PUBLIC, PROTECTED or PRIVATE
-        *You should omit fields to use their default values
-      parameters:
-      - description: ID of memo to update
-        in: path
-        name: memoId
-        required: true
-        type: integer
-      - description: Patched object.
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.PatchMemoRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Stored memo
-          schema:
-            $ref: '#/definitions/store.Memo'
-        "400":
-          description: 'ID is not a number: %s | Malformatted patch memo request |
-            Content size overflow, up to 1MB'
-        "401":
-          description: Missing user in session | Unauthorized
-        "404":
-          description: 'Memo not found: %d'
-        "500":
-          description: Failed to find memo | Failed to patch memo | Failed to upsert
-            memo resource | Failed to delete memo resource | Failed to compose memo
-            response
-      summary: Update a memo
-      tags:
-      - memo
-  /api/v1/memo/{memoId}/organizer:
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: ID of memo to organize
-        in: path
-        name: memoId
-        required: true
-        type: integer
-      - description: Memo organizer object
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoOrganizerRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo information
-          schema:
-            $ref: '#/definitions/store.Memo'
-        "400":
-          description: 'ID is not a number: %s | Malformatted post memo organizer
-            request'
-        "401":
-          description: Missing user in session | Unauthorized
-        "404":
-          description: 'Memo not found: %v'
-        "500":
-          description: 'Failed to find memo | Failed to upsert memo organizer | Failed
-            to find memo by ID: %v | Failed to compose memo response'
-      summary: Organize memo (pin/unpin)
-      tags:
-      - memo-organizer
-  /api/v1/memo/{memoId}/relation:
-    get:
-      consumes:
-      - application/json
-      parameters:
-      - description: ID of memo to find relations
-        in: path
-        name: memoId
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo relation information list
-          schema:
-            items:
-              $ref: '#/definitions/store.MemoRelation'
-            type: array
-        "400":
-          description: 'ID is not a number: %s'
-        "500":
-          description: Failed to list memo relations
-      summary: Get a list of Memo Relations
-      tags:
-      - memo-relation
-    post:
-      consumes:
-      - application/json
-      description: Create a relation between two memos
-      parameters:
-      - description: ID of memo to relate
-        in: path
-        name: memoId
-        required: true
-        type: integer
-      - description: Memo relation object
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertMemoRelationRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo relation information
-          schema:
-            $ref: '#/definitions/store.MemoRelation'
-        "400":
-          description: 'ID is not a number: %s | Malformatted post memo relation request'
-        "500":
-          description: Failed to upsert memo relation
-      summary: Create Memo Relation
-      tags:
-      - memo-relation
-  /api/v1/memo/{memoId}/relation/{relatedMemoId}/type/{relationType}:
-    delete:
-      consumes:
-      - application/json
-      description: Removes a relation between two memos
-      parameters:
-      - description: ID of memo to find relations
-        in: path
-        name: memoId
-        required: true
-        type: integer
-      - description: ID of memo to remove relation to
-        in: path
-        name: relatedMemoId
-        required: true
-        type: integer
-      - description: Type of relation to remove
-        enum:
-        - REFERENCE
-        - COMMENT
-        in: path
-        name: relationType
-        required: true
-        type: string
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo relation deleted
-          schema:
-            type: boolean
-        "400":
-          description: 'Memo ID is not a number: %s | Related memo ID is not a number:
-            %s'
-        "500":
-          description: Failed to delete memo relation
-      summary: Delete a Memo Relation
-      tags:
-      - memo-relation
-  /api/v1/memo/all:
-    get:
-      description: |-
-        This should also list protected memos if the user is logged in
-        Authentication is optional
-      parameters:
-      - description: Limit
-        in: query
-        name: limit
-        type: integer
-      - description: Offset
-        in: query
-        name: offset
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo list
-          schema:
-            items:
-              $ref: '#/definitions/store.Memo'
-            type: array
-        "500":
-          description: Failed to get memo display with updated ts setting value |
-            Failed to fetch all memo list | Failed to compose memo response
-      summary: Get a list of public memos matching optional filters
-      tags:
-      - memo
-  /api/v1/memo/stats:
-    get:
-      description: Used to generate the heatmap
-      parameters:
-      - description: Creator ID
-        in: query
-        name: creatorId
-        type: integer
-      - description: Creator username
-        in: query
-        name: creatorUsername
-        type: string
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Memo createdTs list
-          schema:
-            items:
-              type: integer
-            type: array
-        "400":
-          description: Missing user id to find memo
-        "500":
-          description: Failed to get memo display with updated ts setting value |
-            Failed to find memo list | Failed to compose memo response
-      summary: Get memo stats by creator ID or username
-      tags:
-      - memo
-  /api/v1/ping:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: If succeed to ping the system
-          schema:
-            type: boolean
-      summary: Ping the system
-      tags:
-      - system
-  /api/v1/resource:
-    get:
-      parameters:
-      - description: Limit
-        in: query
-        name: limit
-        type: integer
-      - description: Offset
-        in: query
-        name: offset
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Resource list
-          schema:
-            items:
-              $ref: '#/definitions/store.Resource'
-            type: array
-        "401":
-          description: Missing user in session
-        "500":
-          description: Failed to fetch resource list
-      summary: Get a list of resources
-      tags:
-      - resource
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Request object.
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/api_v1.CreateResourceRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Created resource
-          schema:
-            $ref: '#/definitions/store.Resource'
-        "400":
-          description: Malformatted post resource request | Invalid external link
-            | Invalid external link scheme | Failed to request %s | Failed to read
-            %s | Failed to read mime from %s
-        "401":
-          description: Missing user in session
-        "500":
-          description: Failed to save resource | Failed to create resource | Failed
-            to create activity
-      summary: Create resource
-      tags:
-      - resource
-  /api/v1/resource/{resourceId}:
-    delete:
-      parameters:
-      - description: Resource ID
-        in: path
-        name: resourceId
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Resource deleted
-          schema:
-            type: boolean
-        "400":
-          description: 'ID is not a number: %s'
-        "401":
-          description: Missing user in session
-        "404":
-          description: 'Resource not found: %d'
-        "500":
-          description: Failed to find resource | Failed to delete resource
-      summary: Delete a resource
-      tags:
-      - resource
-    patch:
-      parameters:
-      - description: Resource ID
-        in: path
-        name: resourceId
-        required: true
-        type: integer
-      - description: Patch resource request
-        in: body
-        name: patch
-        required: true
-        schema:
-          $ref: '#/definitions/api_v1.UpdateResourceRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Updated resource
-          schema:
-            $ref: '#/definitions/store.Resource'
-        "400":
-          description: 'ID is not a number: %s | Malformatted patch resource request'
-        "401":
-          description: Missing user in session | Unauthorized
-        "404":
-          description: 'Resource not found: %d'
-        "500":
-          description: Failed to find resource | Failed to patch resource
-      summary: Update a resource
-      tags:
-      - resource
-  /api/v1/resource/blob:
-    post:
-      consumes:
-      - multipart/form-data
-      parameters:
-      - description: File to upload
-        in: formData
-        name: file
-        required: true
-        type: file
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Created resource
-          schema:
-            $ref: '#/definitions/store.Resource'
-        "400":
-          description: Upload file not found | File size exceeds allowed limit of
-            %d MiB | Failed to parse upload data
-        "401":
-          description: Missing user in session
-        "500":
-          description: Failed to get uploading file | Failed to open file | Failed
-            to save resource | Failed to create resource | Failed to create activity
-      summary: Upload resource
-      tags:
-      - resource
-  /api/v1/status:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: System GetSystemStatus
-          schema:
-            $ref: '#/definitions/api_v1.SystemStatus'
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find host user | Failed to find system setting list
-            | Failed to unmarshal system setting customized profile value
-      summary: Get system GetSystemStatus
-      tags:
-      - system
-  /api/v1/storage:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: List of storages
-          schema:
-            items:
-              $ref: '#/definitions/store.Storage'
-            type: array
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to convert storage
-      summary: Get a list of storages
-      tags:
-      - storage
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Request object.
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.CreateStorageRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Created storage
-          schema:
-            $ref: '#/definitions/store.Storage'
-        "400":
-          description: Malformatted post storage request
-        "401":
-          description: Missing user in session
-        "500":
-          description: Failed to find user | Failed to create storage | Failed to
-            convert storage
-      summary: Create storage
-      tags:
-      - storage
-  /api/v1/storage/{storageId}:
-    delete:
-      parameters:
-      - description: Storage ID
-        in: path
-        name: storageId
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Storage deleted
-          schema:
-            type: boolean
-        "400":
-          description: 'ID is not a number: %s | Storage service %d is using'
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to find storage | Failed to unmarshal
-            storage service id | Failed to delete storage
-      summary: Delete a storage
-      tags:
-      - storage
-    patch:
-      parameters:
-      - description: Storage ID
-        in: path
-        name: storageId
-        required: true
-        type: integer
-      - description: Patch request
-        in: body
-        name: patch
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpdateStorageRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Updated resource
-          schema:
-            $ref: '#/definitions/store.Storage'
-        "400":
-          description: 'ID is not a number: %s | Malformatted patch storage request
-            | Malformatted post storage request'
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to patch storage | Failed to convert
-            storage
-      summary: Update a storage
-      tags:
-      - storage
-  /api/v1/system/setting:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: System setting list
-          schema:
-            items:
-              $ref: '#/definitions/api_v1.SystemSetting'
-            type: array
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to find system setting list
-      summary: Get a list of system settings
-      tags:
-      - system-setting
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Request object.
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/api_v1.UpsertSystemSettingRequest'
-      produces:
-      - application/json
-      responses:
-        "400":
-          description: Malformatted post system setting request | invalid system setting
-        "401":
-          description: Missing user in session | Unauthorized
-        "403":
-          description: Cannot disable passwords if no SSO identity provider is configured.
-        "500":
-          description: Failed to find user | Failed to upsert system setting
-      summary: Create system setting
-      tags:
-      - system-setting
-  /api/v1/system/vacuum:
-    post:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Database vacuumed
-          schema:
-            type: boolean
-        "401":
-          description: Missing user in session | Unauthorized
-        "500":
-          description: Failed to find user | Failed to ExecVacuum database
-      summary: Vacuum the database
-      tags:
-      - system
-  /api/v1/tag:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Tag list
-          schema:
-            items:
-              type: string
-            type: array
-        "400":
-          description: Missing user id to find tag
-        "500":
-          description: Failed to find tag list
-      summary: Get a list of tags
-      tags:
-      - tag
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Request object.
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.UpsertTagRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Created tag name
-          schema:
-            type: string
-        "400":
-          description: Malformatted post tag request | Tag name shouldn't be empty
-        "401":
-          description: Missing user in session
-        "500":
-          description: Failed to upsert tag | Failed to create activity
-      summary: Create a tag
-      tags:
-      - tag
-  /api/v1/tag/delete:
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Request object.
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/github_com_usememos_memos_api_v1.DeleteTagRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Tag deleted
-          schema:
-            type: boolean
-        "400":
-          description: Malformatted post tag request | Tag name shouldn't be empty
-        "401":
-          description: Missing user in session
-        "500":
-          description: 'Failed to delete tag name: %v'
-      summary: Delete a tag
-      tags:
-      - tag
-  /api/v1/tag/suggestion:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Tag list
-          schema:
-            items:
-              type: string
-            type: array
-        "400":
-          description: Missing user session
-        "500":
-          description: Failed to find memo list | Failed to find tag list
-      summary: Get a list of tags suggested from other memos contents
-      tags:
-      - tag
-  /api/v1/user:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: User list
-          schema:
-            items:
-              $ref: '#/definitions/store.User'
-            type: array
-        "500":
-          description: Failed to fetch user list
-      summary: Get a list of users
-      tags:
-      - user
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Request object
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/api_v1.CreateUserRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Created user
-          schema:
-            $ref: '#/definitions/store.User'
-        "400":
-          description: Malformatted post user request | Invalid user create format
-        "401":
-          description: Missing auth session | Unauthorized to create user
-        "403":
-          description: Could not create host user
-        "500":
-          description: Failed to find user by id | Failed to generate password hash
-            | Failed to create user | Failed to create activity
-      summary: Create a user
-      tags:
-      - user
-  /api/v1/user/{id}:
-    delete:
-      parameters:
-      - description: User ID
-        in: path
-        name: id
-        required: true
-        type: string
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: User deleted
-          schema:
-            type: boolean
-        "400":
-          description: 'ID is not a number: %s | Current session user not found with
-            ID: %d'
-        "401":
-          description: Missing user in session
-        "403":
-          description: Unauthorized to delete user
-        "500":
-          description: Failed to find user | Failed to delete user
-      summary: Delete a user
-      tags:
-      - user
-    get:
-      parameters:
-      - description: User ID
-        in: path
-        name: id
-        required: true
-        type: integer
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Requested user
-          schema:
-            $ref: '#/definitions/store.User'
-        "400":
-          description: Malformatted user id
-        "404":
-          description: User not found
-        "500":
-          description: Failed to find user
-      summary: Get user by id
-      tags:
-      - user
-    patch:
-      parameters:
-      - description: User ID
-        in: path
-        name: id
-        required: true
-        type: string
-      - description: Patch request
-        in: body
-        name: patch
-        required: true
-        schema:
-          $ref: '#/definitions/api_v1.UpdateUserRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Updated user
-          schema:
-            $ref: '#/definitions/store.User'
-        "400":
-          description: 'ID is not a number: %s | Current session user not found with
-            ID: %d | Malformatted patch user request | Invalid update user request'
-        "401":
-          description: Missing user in session
-        "403":
-          description: Unauthorized to update user
-        "500":
-          description: Failed to find user | Failed to generate password hash | Failed
-            to patch user | Failed to find userSettingList
-      summary: Update a user
-      tags:
-      - user
-  /api/v1/user/me:
-    get:
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Current user
-          schema:
-            $ref: '#/definitions/store.User'
-        "401":
-          description: Missing auth session
-        "500":
-          description: Failed to find user | Failed to find userSettingList
-      summary: Get current user
-      tags:
-      - user
-  /api/v1/user/name/{username}:
-    get:
-      parameters:
-      - description: Username
-        in: path
-        name: username
-        required: true
-        type: string
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: Requested user
-          schema:
-            $ref: '#/definitions/store.User'
-        "404":
-          description: User not found
-        "500":
-          description: Failed to find user
-      summary: Get user by username
-      tags:
-      - user
-  /o/get/GetImage:
-    get:
-      parameters:
-      - description: Image url
-        in: query
-        name: url
-        required: true
-        type: string
-      produces:
-      - GetImage/*
-      responses:
-        "200":
-          description: Image
-        "400":
-          description: 'Missing GetImage url | Wrong url | Failed to get GetImage
-            url: %s'
-        "500":
-          description: Failed to write GetImage blob
-      summary: Get GetImage from URL
-      tags:
-      - image-url
-swagger: "2.0"

programming_language: unknown
filename: system.go
change_type: ModificationType.DELETE
diff: @@ -1,157 +0,0 @@
-package v1
-
-import (
-	"encoding/json"
-	"net/http"
-
-	"github.com/labstack/echo/v4"
-
-	"github.com/usememos/memos/server/profile"
-	"github.com/usememos/memos/store"
-)
-
-type SystemStatus struct {
-	Host    *User           `json:"host"`
-	Profile profile.Profile `json:"profile"`
-	DBSize  int64           `json:"dbSize"`
-
-	// System settings
-	// Disable password login.
-	DisablePasswordLogin bool `json:"disablePasswordLogin"`
-	// Disable public memos.
-	DisablePublicMemos bool `json:"disablePublicMemos"`
-	// Max upload size.
-	MaxUploadSizeMiB int `json:"maxUploadSizeMiB"`
-	// Customized server profile, including server name and external url.
-	CustomizedProfile CustomizedProfile `json:"customizedProfile"`
-	// Storage service ID.
-	StorageServiceID int32 `json:"storageServiceId"`
-	// Local storage path.
-	LocalStoragePath string `json:"localStoragePath"`
-	// Memo display with updated timestamp.
-	MemoDisplayWithUpdatedTs bool `json:"memoDisplayWithUpdatedTs"`
-}
-
-func (s *APIV1Service) registerSystemRoutes(g *echo.Group) {
-	g.GET("/ping", s.PingSystem)
-	g.GET("/status", s.GetSystemStatus)
-	g.POST("/system/vacuum", s.ExecVacuum)
-}
-
-// PingSystem godoc
-//
-//	@Summary	Ping the system
-//	@Tags		system
-//	@Produce	json
-//	@Success	200	{boolean}	true	"If succeed to ping the system"
-//	@Router		/api/v1/ping [GET]
-func (*APIV1Service) PingSystem(c echo.Context) error {
-	return c.JSON(http.StatusOK, true)
-}
-
-// GetSystemStatus godoc
-//
-//	@Summary	Get system GetSystemStatus
-//	@Tags		system
-//	@Produce	json
-//	@Success	200	{object}	SystemStatus	"System GetSystemStatus"
-//	@Failure	401	{object}	nil				"Missing user in session | Unauthorized"
-//	@Failure	500	{object}	nil				"Failed to find host user | Failed to find system setting list | Failed to unmarshal system setting customized profile value"
-//	@Router		/api/v1/status [GET]
-func (s *APIV1Service) GetSystemStatus(c echo.Context) error {
-	ctx := c.Request().Context()
-
-	systemStatus := SystemStatus{
-		Profile: profile.Profile{
-			Mode:    s.Profile.Mode,
-			Version: s.Profile.Version,
-		},
-		MaxUploadSizeMiB: 32,
-		CustomizedProfile: CustomizedProfile{
-			Name:       "Memos",
-			Locale:     "en",
-			Appearance: "system",
-		},
-		StorageServiceID: DefaultStorage,
-		LocalStoragePath: "assets/{timestamp}_{filename}",
-	}
-
-	hostUserType := store.RoleHost
-	hostUser, err := s.Store.GetUser(ctx, &store.FindUser{
-		Role: &hostUserType,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find host user").SetInternal(err)
-	}
-	if hostUser != nil {
-		systemStatus.Host = &User{ID: hostUser.ID}
-	}
-
-	workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace general setting").SetInternal(err)
-	}
-	systemStatus.DisablePasswordLogin = workspaceGeneralSetting.DisallowPasswordLogin
-
-	systemSettingList, err := s.Store.ListWorkspaceSettings(ctx, &store.FindWorkspaceSetting{})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
-	}
-	for _, systemSetting := range systemSettingList {
-		var baseValue any
-		err := json.Unmarshal([]byte(systemSetting.Value), &baseValue)
-		if err != nil {
-			// Skip invalid value.
-			continue
-		}
-
-		switch systemSetting.Name {
-		case SystemSettingMaxUploadSizeMiBName.String():
-			systemStatus.MaxUploadSizeMiB = int(baseValue.(float64))
-		case SystemSettingCustomizedProfileName.String():
-			customizedProfile := CustomizedProfile{}
-			if err := json.Unmarshal([]byte(systemSetting.Value), &customizedProfile); err != nil {
-				return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting customized profile value").SetInternal(err)
-			}
-			systemStatus.CustomizedProfile = customizedProfile
-		case SystemSettingStorageServiceIDName.String():
-			systemStatus.StorageServiceID = int32(baseValue.(float64))
-		default:
-			// Skip unknown system setting.
-		}
-	}
-
-	return c.JSON(http.StatusOK, systemStatus)
-}
-
-// ExecVacuum godoc
-//
-//	@Summary	Vacuum the database
-//	@Tags		system
-//	@Produce	json
-//	@Success	200	{boolean}	true	"Database vacuumed"
-//	@Failure	401	{object}	nil		"Missing user in session | Unauthorized"
-//	@Failure	500	{object}	nil		"Failed to find user | Failed to ExecVacuum database"
-//	@Router		/api/v1/system/vacuum [POST]
-func (s *APIV1Service) ExecVacuum(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil || user.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	if err := s.Store.Vacuum(ctx); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to vacuum database").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, true)
-}

programming_language: unknown
filename: system_setting.go
change_type: ModificationType.DELETE
diff: @@ -1,211 +0,0 @@
-package v1
-
-import (
-	"encoding/json"
-	"net/http"
-	"path/filepath"
-	"strings"
-
-	"github.com/labstack/echo/v4"
-	"github.com/pkg/errors"
-
-	"github.com/usememos/memos/store"
-)
-
-type SystemSettingName string
-
-const (
-	// SystemSettingMaxUploadSizeMiBName is the name of max upload size setting.
-	SystemSettingMaxUploadSizeMiBName SystemSettingName = "max-upload-size-mib"
-	// SystemSettingCustomizedProfileName is the name of customized server profile.
-	SystemSettingCustomizedProfileName SystemSettingName = "customized-profile"
-	// SystemSettingStorageServiceIDName is the name of storage service ID.
-	SystemSettingStorageServiceIDName SystemSettingName = "storage-service-id"
-	// SystemSettingLocalStoragePathName is the name of local storage path.
-	SystemSettingLocalStoragePathName SystemSettingName = "local-storage-path"
-)
-const systemSettingUnmarshalError = `failed to unmarshal value from system setting "%v"`
-
-// CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
-type CustomizedProfile struct {
-	// Name is the server name, default is `memos`
-	Name string `json:"name"`
-	// LogoURL is the url of logo image.
-	LogoURL string `json:"logoUrl"`
-	// Description is the server description.
-	Description string `json:"description"`
-	// Locale is the server default locale.
-	Locale string `json:"locale"`
-	// Appearance is the server default appearance.
-	Appearance string `json:"appearance"`
-}
-
-func (key SystemSettingName) String() string {
-	return string(key)
-}
-
-type SystemSetting struct {
-	Name SystemSettingName `json:"name"`
-	// Value is a JSON string with basic value.
-	Value       string `json:"value"`
-	Description string `json:"description"`
-}
-
-type UpsertSystemSettingRequest struct {
-	Name        SystemSettingName `json:"name"`
-	Value       string            `json:"value"`
-	Description string            `json:"description"`
-}
-
-func (s *APIV1Service) registerSystemSettingRoutes(g *echo.Group) {
-	g.GET("/system/setting", s.GetSystemSettingList)
-	g.POST("/system/setting", s.CreateSystemSetting)
-}
-
-// GetSystemSettingList godoc
-//
-//	@Summary	Get a list of system settings
-//	@Tags		system-setting
-//	@Produce	json
-//	@Success	200	{object}	[]SystemSetting	"System setting list"
-//	@Failure	401	{object}	nil				"Missing user in session | Unauthorized"
-//	@Failure	500	{object}	nil				"Failed to find user | Failed to find system setting list"
-//	@Router		/api/v1/system/setting [GET]
-func (s *APIV1Service) GetSystemSettingList(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil || user.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	list, err := s.Store.ListWorkspaceSettings(ctx, &store.FindWorkspaceSetting{})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
-	}
-
-	systemSettingList := make([]*SystemSetting, 0, len(list))
-	for _, systemSetting := range list {
-		systemSettingList = append(systemSettingList, convertSystemSettingFromStore(systemSetting))
-	}
-	return c.JSON(http.StatusOK, systemSettingList)
-}
-
-// CreateSystemSetting godoc
-//
-//	@Summary	Create system setting
-//	@Tags		system-setting
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		UpsertSystemSettingRequest	true	"Request object."
-//	@Failure	400		{object}	nil							"Malformatted post system setting request | invalid system setting"
-//	@Failure	401		{object}	nil							"Missing user in session | Unauthorized"
-//	@Failure	403		{object}	nil							"Cannot disable passwords if no SSO identity provider is configured."
-//	@Failure	500		{object}	nil							"Failed to find user | Failed to upsert system setting"
-//	@Router		/api/v1/system/setting [POST]
-func (s *APIV1Service) CreateSystemSetting(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil || user.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
-	}
-
-	systemSettingUpsert := &UpsertSystemSettingRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(systemSettingUpsert); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post system setting request").SetInternal(err)
-	}
-	if err := systemSettingUpsert.Validate(); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "invalid system setting").SetInternal(err)
-	}
-
-	systemSetting, err := s.Store.UpsertWorkspaceSetting(ctx, &store.WorkspaceSetting{
-		Name:        systemSettingUpsert.Name.String(),
-		Value:       systemSettingUpsert.Value,
-		Description: systemSettingUpsert.Description,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, convertSystemSettingFromStore(systemSetting))
-}
-
-func (upsert UpsertSystemSettingRequest) Validate() error {
-	switch settingName := upsert.Name; settingName {
-	case SystemSettingMaxUploadSizeMiBName:
-		var value int
-		if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
-			return errors.Errorf(systemSettingUnmarshalError, settingName)
-		}
-	case SystemSettingCustomizedProfileName:
-		customizedProfile := CustomizedProfile{
-			Name:        "Memos",
-			LogoURL:     "",
-			Description: "",
-			Locale:      "en",
-			Appearance:  "system",
-		}
-		if err := json.Unmarshal([]byte(upsert.Value), &customizedProfile); err != nil {
-			return errors.Errorf(systemSettingUnmarshalError, settingName)
-		}
-	case SystemSettingStorageServiceIDName:
-		// Note: 0 is the default value(database) for storage service ID.
-		value := 0
-		if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
-			return errors.Errorf(systemSettingUnmarshalError, settingName)
-		}
-		return nil
-	case SystemSettingLocalStoragePathName:
-		value := ""
-		if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
-			return errors.Errorf(systemSettingUnmarshalError, settingName)
-		}
-
-		trimmedValue := strings.TrimSpace(value)
-		switch {
-		case trimmedValue != value:
-			return errors.New("local storage path must not contain leading or trailing whitespace")
-		case trimmedValue == "":
-			return errors.New("local storage path can't be empty")
-		case strings.Contains(trimmedValue, "\\"):
-			return errors.New("local storage path must use forward slashes `/`")
-		case strings.Contains(trimmedValue, "../"):
-			return errors.New("local storage path is not allowed to contain `../`")
-		case strings.HasPrefix(trimmedValue, "./"):
-			return errors.New("local storage path is not allowed to start with `./`")
-		case filepath.IsAbs(trimmedValue) || trimmedValue[0] == '/':
-			return errors.New("local storage path must be a relative path")
-		case !strings.Contains(trimmedValue, "{filename}"):
-			return errors.New("local storage path must contain `{filename}`")
-		}
-	default:
-		return errors.New("invalid system setting name")
-	}
-	return nil
-}
-
-func convertSystemSettingFromStore(systemSetting *store.WorkspaceSetting) *SystemSetting {
-	return &SystemSetting{
-		Name:        SystemSettingName(systemSetting.Name),
-		Value:       systemSetting.Value,
-		Description: systemSetting.Description,
-	}
-}

programming_language: unknown
filename: tag.go
change_type: ModificationType.DELETE
diff: @@ -1,218 +0,0 @@
-package v1
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"regexp"
-	"slices"
-	"sort"
-
-	"github.com/labstack/echo/v4"
-
-	"github.com/usememos/memos/store"
-)
-
-type Tag struct {
-	Name      string
-	CreatorID int32
-}
-
-type UpsertTagRequest struct {
-	Name string `json:"name"`
-}
-
-type DeleteTagRequest struct {
-	Name string `json:"name"`
-}
-
-func (s *APIV1Service) registerTagRoutes(g *echo.Group) {
-	g.GET("/tag", s.GetTagList)
-	g.POST("/tag", s.CreateTag)
-	g.GET("/tag/suggestion", s.GetTagSuggestion)
-	g.POST("/tag/delete", s.DeleteTag)
-}
-
-// GetTagList godoc
-//
-//	@Summary	Get a list of tags
-//	@Tags		tag
-//	@Produce	json
-//	@Success	200	{object}	[]string	"Tag list"
-//	@Failure	400	{object}	nil			"Missing user id to find tag"
-//	@Failure	500	{object}	nil			"Failed to find tag list"
-//	@Router		/api/v1/tag [GET]
-func (s *APIV1Service) GetTagList(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find tag")
-	}
-
-	list, err := s.Store.ListTags(ctx, &store.FindTag{
-		CreatorID: userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find tag list").SetInternal(err)
-	}
-
-	tagNameList := []string{}
-	for _, tag := range list {
-		tagNameList = append(tagNameList, tag.Name)
-	}
-	return c.JSON(http.StatusOK, tagNameList)
-}
-
-// CreateTag godoc
-//
-//	@Summary	Create a tag
-//	@Tags		tag
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		UpsertTagRequest	true	"Request object."
-//	@Success	200		{object}	string				"Created tag name"
-//	@Failure	400		{object}	nil					"Malformatted post tag request | Tag name shouldn't be empty"
-//	@Failure	401		{object}	nil					"Missing user in session"
-//	@Failure	500		{object}	nil					"Failed to upsert tag | Failed to create activity"
-//	@Router		/api/v1/tag [POST]
-func (s *APIV1Service) CreateTag(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	tagUpsert := &UpsertTagRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(tagUpsert); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post tag request").SetInternal(err)
-	}
-	if tagUpsert.Name == "" {
-		return echo.NewHTTPError(http.StatusBadRequest, "Tag name shouldn't be empty")
-	}
-
-	tag, err := s.Store.UpsertTag(ctx, &store.Tag{
-		Name:      tagUpsert.Name,
-		CreatorID: userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert tag").SetInternal(err)
-	}
-	tagMessage := convertTagFromStore(tag)
-	return c.JSON(http.StatusOK, tagMessage.Name)
-}
-
-// DeleteTag godoc
-//
-//	@Summary	Delete a tag
-//	@Tags		tag
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		DeleteTagRequest	true	"Request object."
-//	@Success	200		{boolean}	true				"Tag deleted"
-//	@Failure	400		{object}	nil					"Malformatted post tag request | Tag name shouldn't be empty"
-//	@Failure	401		{object}	nil					"Missing user in session"
-//	@Failure	500		{object}	nil					"Failed to delete tag name: %v"
-//	@Router		/api/v1/tag/delete [POST]
-func (s *APIV1Service) DeleteTag(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-
-	tagDelete := &DeleteTagRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(tagDelete); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post tag request").SetInternal(err)
-	}
-	if tagDelete.Name == "" {
-		return echo.NewHTTPError(http.StatusBadRequest, "Tag name shouldn't be empty")
-	}
-
-	err := s.Store.DeleteTag(ctx, &store.DeleteTag{
-		Name:      tagDelete.Name,
-		CreatorID: userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete tag name: %v", tagDelete.Name)).SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, true)
-}
-
-// GetTagSuggestion godoc
-//
-//	@Summary	Get a list of tags suggested from other memos contents
-//	@Tags		tag
-//	@Produce	json
-//	@Success	200	{object}	[]string	"Tag list"
-//	@Failure	400	{object}	nil			"Missing user session"
-//	@Failure	500	{object}	nil			"Failed to find memo list | Failed to find tag list"
-//	@Router		/api/v1/tag/suggestion [GET]
-func (s *APIV1Service) GetTagSuggestion(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusBadRequest, "Missing user session")
-	}
-	normalRowStatus := store.Normal
-	memoFind := &store.FindMemo{
-		CreatorID:     &userID,
-		ContentSearch: []string{"#"},
-		RowStatus:     &normalRowStatus,
-	}
-
-	memoMessageList, err := s.Store.ListMemos(ctx, memoFind)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
-	}
-
-	list, err := s.Store.ListTags(ctx, &store.FindTag{
-		CreatorID: userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find tag list").SetInternal(err)
-	}
-	tagNameList := []string{}
-	for _, tag := range list {
-		tagNameList = append(tagNameList, tag.Name)
-	}
-
-	tagMapSet := make(map[string]bool)
-	for _, memo := range memoMessageList {
-		for _, tag := range findTagListFromMemoContent(memo.Content) {
-			if !slices.Contains(tagNameList, tag) {
-				tagMapSet[tag] = true
-			}
-		}
-	}
-	tagList := []string{}
-	for tag := range tagMapSet {
-		tagList = append(tagList, tag)
-	}
-	sort.Strings(tagList)
-	return c.JSON(http.StatusOK, tagList)
-}
-
-func convertTagFromStore(tag *store.Tag) *Tag {
-	return &Tag{
-		Name:      tag.Name,
-		CreatorID: tag.CreatorID,
-	}
-}
-
-var tagRegexp = regexp.MustCompile(`#([^\s#,]+)`)
-
-func findTagListFromMemoContent(memoContent string) []string {
-	tagMapSet := make(map[string]bool)
-	matches := tagRegexp.FindAllStringSubmatch(memoContent, -1)
-	for _, v := range matches {
-		tagName := v[1]
-		tagMapSet[tagName] = true
-	}
-
-	tagList := []string{}
-	for tag := range tagMapSet {
-		tagList = append(tagList, tag)
-	}
-	sort.Strings(tagList)
-	return tagList
-}

programming_language: unknown
filename: tag_test.go
change_type: ModificationType.DELETE
diff: @@ -1,47 +0,0 @@
-package v1
-
-import (
-	"testing"
-)
-
-func TestFindTagListFromMemoContent(t *testing.T) {
-	tests := []struct {
-		memoContent string
-		want        []string
-	}{
-		{
-			memoContent: "#tag1 ",
-			want:        []string{"tag1"},
-		},
-		{
-			memoContent: "#tag1 #tag2 ",
-			want:        []string{"tag1", "tag2"},
-		},
-		{
-			memoContent: "#tag1 #tag2 \n#tag3 ",
-			want:        []string{"tag1", "tag2", "tag3"},
-		},
-		{
-			memoContent: "#tag1 #tag2 \n#tag3 #tag4 ",
-			want:        []string{"tag1", "tag2", "tag3", "tag4"},
-		},
-		{
-			memoContent: "#tag1 #tag2 \n#tag3  #tag4 ",
-			want:        []string{"tag1", "tag2", "tag3", "tag4"},
-		},
-		{
-			memoContent: "#tag1 123123#tag2 \n#tag3  #tag4 ",
-			want:        []string{"tag1", "tag2", "tag3", "tag4"},
-		},
-		{
-			memoContent: "#tag1 http://123123.com?123123#tag2 \n#tag3  #tag4 http://123123.com?123123#tag2) ",
-			want:        []string{"tag1", "tag2", "tag2)", "tag3", "tag4"},
-		},
-	}
-	for _, test := range tests {
-		result := findTagListFromMemoContent(test.memoContent)
-		if len(result) != len(test.want) {
-			t.Errorf("Find tag list %s: got result %v, want %v.", test.memoContent, result, test.want)
-		}
-	}
-}

programming_language: unknown
filename: user.go
change_type: ModificationType.DELETE
diff: @@ -1,487 +0,0 @@
-package v1
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"strings"
-	"time"
-
-	"github.com/labstack/echo/v4"
-	"github.com/pkg/errors"
-	"golang.org/x/crypto/bcrypt"
-
-	"github.com/usememos/memos/internal/util"
-	"github.com/usememos/memos/store"
-)
-
-// Role is the type of a role.
-type Role string
-
-const (
-	// RoleHost is the HOST role.
-	RoleHost Role = "HOST"
-	// RoleAdmin is the ADMIN role.
-	RoleAdmin Role = "ADMIN"
-	// RoleUser is the USER role.
-	RoleUser Role = "USER"
-)
-
-func (role Role) String() string {
-	return string(role)
-}
-
-type User struct {
-	ID int32 `json:"id"`
-
-	// Standard fields
-	RowStatus RowStatus `json:"rowStatus"`
-	CreatedTs int64     `json:"createdTs"`
-	UpdatedTs int64     `json:"updatedTs"`
-
-	// Domain specific fields
-	Username     string `json:"username"`
-	Role         Role   `json:"role"`
-	Email        string `json:"email"`
-	Nickname     string `json:"nickname"`
-	PasswordHash string `json:"-"`
-	AvatarURL    string `json:"avatarUrl"`
-}
-
-type CreateUserRequest struct {
-	Username string `json:"username"`
-	Role     Role   `json:"role"`
-	Email    string `json:"email"`
-	Nickname string `json:"nickname"`
-	Password string `json:"password"`
-}
-
-type UpdateUserRequest struct {
-	RowStatus *RowStatus `json:"rowStatus"`
-	Username  *string    `json:"username"`
-	Email     *string    `json:"email"`
-	Nickname  *string    `json:"nickname"`
-	Password  *string    `json:"password"`
-	AvatarURL *string    `json:"avatarUrl"`
-}
-
-func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
-	g.GET("/user", s.GetUserList)
-	g.POST("/user", s.CreateUser)
-	g.GET("/user/me", s.GetCurrentUser)
-	// NOTE: This should be moved to /api/v2/user/:username
-	g.GET("/user/name/:username", s.GetUserByUsername)
-	g.GET("/user/:id", s.GetUserByID)
-	g.PATCH("/user/:id", s.UpdateUser)
-	g.DELETE("/user/:id", s.DeleteUser)
-}
-
-// GetUserList godoc
-//
-//	@Summary	Get a list of users
-//	@Tags		user
-//	@Produce	json
-//	@Success	200	{object}	[]store.User	"User list"
-//	@Failure	500	{object}	nil				"Failed to fetch user list"
-//	@Router		/api/v1/user [GET]
-func (s *APIV1Service) GetUserList(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
-	}
-	currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
-	}
-	if currentUser == nil {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
-	}
-	if currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to list users")
-	}
-
-	list, err := s.Store.ListUsers(ctx, &store.FindUser{})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user list").SetInternal(err)
-	}
-
-	userMessageList := make([]*User, 0, len(list))
-	for _, user := range list {
-		userMessage := convertUserFromStore(user)
-		// data desensitize
-		userMessage.Email = ""
-		userMessageList = append(userMessageList, userMessage)
-	}
-	return c.JSON(http.StatusOK, userMessageList)
-}
-
-// CreateUser godoc
-//
-//	@Summary	Create a user
-//	@Tags		user
-//	@Accept		json
-//	@Produce	json
-//	@Param		body	body		CreateUserRequest	true	"Request object"
-//	@Success	200		{object}	store.User			"Created user"
-//	@Failure	400		{object}	nil					"Malformatted post user request | Invalid user create format"
-//	@Failure	401		{object}	nil					"Missing auth session | Unauthorized to create user"
-//	@Failure	403		{object}	nil					"Could not create host user"
-//	@Failure	500		{object}	nil					"Failed to find user by id | Failed to generate password hash | Failed to create user | Failed to create activity"
-//	@Router		/api/v1/user [POST]
-func (s *APIV1Service) CreateUser(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
-	}
-	currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &userID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
-	}
-	if currentUser == nil {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
-	}
-	if currentUser.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to create user")
-	}
-
-	userCreate := &CreateUserRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(userCreate); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user request").SetInternal(err)
-	}
-	if err := userCreate.Validate(); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
-	}
-	if !util.UIDMatcher.MatchString(strings.ToLower(userCreate.Username)) {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", userCreate.Username)).SetInternal(err)
-	}
-	// Disallow host user to be created.
-	if userCreate.Role == RoleHost {
-		return echo.NewHTTPError(http.StatusForbidden, "Could not create host user")
-	}
-
-	passwordHash, err := bcrypt.GenerateFromPassword([]byte(userCreate.Password), bcrypt.DefaultCost)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
-	}
-
-	user, err := s.Store.CreateUser(ctx, &store.User{
-		Username:     userCreate.Username,
-		Role:         store.Role(userCreate.Role),
-		Email:        userCreate.Email,
-		Nickname:     userCreate.Nickname,
-		PasswordHash: string(passwordHash),
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
-	}
-
-	userMessage := convertUserFromStore(user)
-	return c.JSON(http.StatusOK, userMessage)
-}
-
-// GetCurrentUser godoc
-//
-//	@Summary	Get current user
-//	@Tags		user
-//	@Produce	json
-//	@Success	200	{object}	store.User	"Current user"
-//	@Failure	401	{object}	nil			"Missing auth session"
-//	@Failure	500	{object}	nil			"Failed to find user | Failed to find userSettingList"
-//	@Router		/api/v1/user/me [GET]
-func (s *APIV1Service) GetCurrentUser(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &userID})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
-	}
-
-	userMessage := convertUserFromStore(user)
-	return c.JSON(http.StatusOK, userMessage)
-}
-
-// GetUserByUsername godoc
-//
-//	@Summary	Get user by username
-//	@Tags		user
-//	@Produce	json
-//	@Param		username	path		string		true	"Username"
-//	@Success	200			{object}	store.User	"Requested user"
-//	@Failure	404			{object}	nil			"User not found"
-//	@Failure	500			{object}	nil			"Failed to find user"
-//	@Router		/api/v1/user/name/{username} [GET]
-func (s *APIV1Service) GetUserByUsername(c echo.Context) error {
-	ctx := c.Request().Context()
-	username := c.Param("username")
-	user, err := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil {
-		return echo.NewHTTPError(http.StatusNotFound, "User not found")
-	}
-
-	userMessage := convertUserFromStore(user)
-	// data desensitize
-	userMessage.Email = ""
-	return c.JSON(http.StatusOK, userMessage)
-}
-
-// GetUserByID godoc
-//
-//	@Summary	Get user by id
-//	@Tags		user
-//	@Produce	json
-//	@Param		id	path		int			true	"User ID"
-//	@Success	200	{object}	store.User	"Requested user"
-//	@Failure	400	{object}	nil			"Malformatted user id"
-//	@Failure	404	{object}	nil			"User not found"
-//	@Failure	500	{object}	nil			"Failed to find user"
-//	@Router		/api/v1/user/{id} [GET]
-func (s *APIV1Service) GetUserByID(c echo.Context) error {
-	ctx := c.Request().Context()
-	id, err := util.ConvertStringToInt32(c.Param("id"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err)
-	}
-
-	user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &id})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if user == nil {
-		return echo.NewHTTPError(http.StatusNotFound, "User not found")
-	}
-
-	userMessage := convertUserFromStore(user)
-	userID, ok := c.Get(userIDContextKey).(int32)
-	if !ok || userID != user.ID {
-		// Data desensitize.
-		userMessage.Email = ""
-	}
-
-	return c.JSON(http.StatusOK, userMessage)
-}
-
-// DeleteUser godoc
-//
-//	@Summary	Delete a user
-//	@Tags		user
-//	@Produce	json
-//	@Param		id	path		string	true	"User ID"
-//	@Success	200	{boolean}	true	"User deleted"
-//	@Failure	400	{object}	nil		"ID is not a number: %s | Current session user not found with ID: %d"
-//	@Failure	401	{object}	nil		"Missing user in session"
-//	@Failure	403	{object}	nil		"Unauthorized to delete user"
-//	@Failure	500	{object}	nil		"Failed to find user | Failed to delete user"
-//	@Router		/api/v1/user/{id} [DELETE]
-func (s *APIV1Service) DeleteUser(c echo.Context) error {
-	ctx := c.Request().Context()
-	currentUserID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-	currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
-		ID: &currentUserID,
-	})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if currentUser == nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
-	} else if currentUser.Role != store.RoleHost {
-		return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to delete user").SetInternal(err)
-	}
-
-	userID, err := util.ConvertStringToInt32(c.Param("id"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
-	}
-	if currentUserID == userID {
-		return echo.NewHTTPError(http.StatusBadRequest, "Cannot delete current user")
-	}
-
-	if err := s.Store.DeleteUser(ctx, &store.DeleteUser{
-		ID: userID,
-	}); err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, true)
-}
-
-// UpdateUser godoc
-//
-//	@Summary	Update a user
-//	@Tags		user
-//	@Produce	json
-//	@Param		id		path		string				true	"User ID"
-//	@Param		patch	body		UpdateUserRequest	true	"Patch request"
-//	@Success	200		{object}	store.User			"Updated user"
-//	@Failure	400		{object}	nil					"ID is not a number: %s | Current session user not found with ID: %d | Malformatted patch user request | Invalid update user request"
-//	@Failure	401		{object}	nil					"Missing user in session"
-//	@Failure	403		{object}	nil					"Unauthorized to update user"
-//	@Failure	500		{object}	nil					"Failed to find user | Failed to generate password hash | Failed to patch user | Failed to find userSettingList"
-//	@Router		/api/v1/user/{id} [PATCH]
-func (s *APIV1Service) UpdateUser(c echo.Context) error {
-	ctx := c.Request().Context()
-	userID, err := util.ConvertStringToInt32(c.Param("id"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
-	}
-
-	currentUserID, ok := c.Get(userIDContextKey).(int32)
-	if !ok {
-		return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
-	}
-	currentUser, err := s.Store.GetUser(ctx, &store.FindUser{ID: &currentUserID})
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
-	}
-	if currentUser == nil {
-		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
-	} else if currentUser.Role != store.RoleHost && currentUserID != userID {
-		return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to update user").SetInternal(err)
-	}
-
-	request := &UpdateUserRequest{}
-	if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
-	}
-	if err := request.Validate(); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Invalid update user request").SetInternal(err)
-	}
-
-	currentTs := time.Now().Unix()
-	userUpdate := &store.UpdateUser{
-		ID:        userID,
-		UpdatedTs: &currentTs,
-	}
-	if request.RowStatus != nil {
-		rowStatus := store.RowStatus(request.RowStatus.String())
-		userUpdate.RowStatus = &rowStatus
-		if rowStatus == store.Archived && currentUserID == userID {
-			return echo.NewHTTPError(http.StatusBadRequest, "Cannot archive current user")
-		}
-	}
-	if request.Username != nil {
-		if !util.UIDMatcher.MatchString(strings.ToLower(*request.Username)) {
-			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", *request.Username)).SetInternal(err)
-		}
-		userUpdate.Username = request.Username
-	}
-	if request.Email != nil {
-		userUpdate.Email = request.Email
-	}
-	if request.Nickname != nil {
-		userUpdate.Nickname = request.Nickname
-	}
-	if request.Password != nil {
-		passwordHash, err := bcrypt.GenerateFromPassword([]byte(*request.Password), bcrypt.DefaultCost)
-		if err != nil {
-			return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
-		}
-
-		passwordHashStr := string(passwordHash)
-		userUpdate.PasswordHash = &passwordHashStr
-	}
-	if request.AvatarURL != nil {
-		userUpdate.AvatarURL = request.AvatarURL
-	}
-
-	user, err := s.Store.UpdateUser(ctx, userUpdate)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
-	}
-
-	userMessage := convertUserFromStore(user)
-	return c.JSON(http.StatusOK, userMessage)
-}
-
-func (create CreateUserRequest) Validate() error {
-	if len(create.Username) < 3 {
-		return errors.New("username is too short, minimum length is 3")
-	}
-	if len(create.Username) > 32 {
-		return errors.New("username is too long, maximum length is 32")
-	}
-	if len(create.Password) < 3 {
-		return errors.New("password is too short, minimum length is 3")
-	}
-	if len(create.Password) > 512 {
-		return errors.New("password is too long, maximum length is 512")
-	}
-	if len(create.Nickname) > 64 {
-		return errors.New("nickname is too long, maximum length is 64")
-	}
-	if create.Email != "" {
-		if len(create.Email) > 256 {
-			return errors.New("email is too long, maximum length is 256")
-		}
-		if !util.ValidateEmail(create.Email) {
-			return errors.New("invalid email format")
-		}
-	}
-
-	return nil
-}
-
-func (update UpdateUserRequest) Validate() error {
-	if update.Username != nil && len(*update.Username) < 3 {
-		return errors.New("username is too short, minimum length is 3")
-	}
-	if update.Username != nil && len(*update.Username) > 32 {
-		return errors.New("username is too long, maximum length is 32")
-	}
-	if update.Password != nil && len(*update.Password) < 3 {
-		return errors.New("password is too short, minimum length is 3")
-	}
-	if update.Password != nil && len(*update.Password) > 512 {
-		return errors.New("password is too long, maximum length is 512")
-	}
-	if update.Nickname != nil && len(*update.Nickname) > 64 {
-		return errors.New("nickname is too long, maximum length is 64")
-	}
-	if update.AvatarURL != nil {
-		if len(*update.AvatarURL) > 2<<20 {
-			return errors.New("avatar is too large, maximum is 2MB")
-		}
-	}
-	if update.Email != nil && *update.Email != "" {
-		if len(*update.Email) > 256 {
-			return errors.New("email is too long, maximum length is 256")
-		}
-		if !util.ValidateEmail(*update.Email) {
-			return errors.New("invalid email format")
-		}
-	}
-
-	return nil
-}
-
-func convertUserFromStore(user *store.User) *User {
-	return &User{
-		ID:           user.ID,
-		RowStatus:    RowStatus(user.RowStatus),
-		CreatedTs:    user.CreatedTs,
-		UpdatedTs:    user.UpdatedTs,
-		Username:     user.Username,
-		Role:         Role(user.Role),
-		Email:        user.Email,
-		Nickname:     user.Nickname,
-		PasswordHash: user.PasswordHash,
-		AvatarURL:    user.AvatarURL,
-	}
-}

programming_language: unknown
filename: v1.go
change_type: ModificationType.DELETE
diff: @@ -1,94 +0,0 @@
-package v1
-
-import (
-	"net/http"
-	"time"
-
-	"github.com/labstack/echo/v4"
-	"github.com/labstack/echo/v4/middleware"
-
-	"github.com/usememos/memos/plugin/telegram"
-	"github.com/usememos/memos/server/profile"
-	"github.com/usememos/memos/server/route/resource"
-	"github.com/usememos/memos/server/route/rss"
-	"github.com/usememos/memos/store"
-)
-
-type APIV1Service struct {
-	Secret      string
-	Profile     *profile.Profile
-	Store       *store.Store
-	telegramBot *telegram.Bot
-}
-
-// @title						memos API
-// @version					1.0
-// @description				A privacy-first, lightweight note-taking service.
-//
-// @contact.name				API Support
-// @contact.url				https://github.com/orgs/usememos/discussions
-//
-// @license.name				MIT License
-// @license.url				https://github.com/usememos/memos/blob/main/LICENSE
-//
-// @BasePath					/
-//
-// @externalDocs.url			https://usememos.com/
-// @externalDocs.description	Find out more about Memos.
-func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store, telegramBot *telegram.Bot) *APIV1Service {
-	return &APIV1Service{
-		Secret:      secret,
-		Profile:     profile,
-		Store:       store,
-		telegramBot: telegramBot,
-	}
-}
-
-func (s *APIV1Service) Register(rootGroup *echo.Group) {
-	// Register API v1 routes.
-	apiV1Group := rootGroup.Group("/api/v1")
-	apiV1Group.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
-		Store: middleware.NewRateLimiterMemoryStoreWithConfig(
-			middleware.RateLimiterMemoryStoreConfig{Rate: 30, Burst: 100, ExpiresIn: 3 * time.Minute},
-		),
-		IdentifierExtractor: func(ctx echo.Context) (string, error) {
-			id := ctx.RealIP()
-			return id, nil
-		},
-		ErrorHandler: func(context echo.Context, err error) error {
-			return context.JSON(http.StatusForbidden, nil)
-		},
-		DenyHandler: func(context echo.Context, identifier string, err error) error {
-			return context.JSON(http.StatusTooManyRequests, nil)
-		},
-	}))
-	apiV1Group.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
-		return JWTMiddleware(s, next, s.Secret)
-	})
-	s.registerSystemRoutes(apiV1Group)
-	s.registerSystemSettingRoutes(apiV1Group)
-	s.registerAuthRoutes(apiV1Group)
-	s.registerUserRoutes(apiV1Group)
-	s.registerTagRoutes(apiV1Group)
-	s.registerStorageRoutes(apiV1Group)
-	s.registerResourceRoutes(apiV1Group)
-	s.registerMemoRoutes(apiV1Group)
-	s.registerMemoOrganizerRoutes(apiV1Group)
-	s.registerMemoRelationRoutes(apiV1Group)
-
-	// Register public routes.
-	publicGroup := rootGroup.Group("/o")
-	publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
-		return JWTMiddleware(s, next, s.Secret)
-	})
-	s.registerGetterPublicRoutes(publicGroup)
-
-	// Create and register resource public routes.
-	resource.NewResourceService(s.Profile, s.Store).RegisterRoutes(publicGroup)
-
-	// Create and register rss public routes.
-	rss.NewRSSService(s.Profile, s.Store).RegisterRoutes(rootGroup)
-
-	// programmatically set API version same as the server version
-	SwaggerInfo.Version = s.Profile.Version
-}

programming_language: unknown
filename: storage_service.go
change_type: ModificationType.MODIFY
diff: @@ -25,7 +25,7 @@ func (s *APIV2Service) CreateStorage(ctx context.Context, request *apiv2pb.Creat
 		return nil, status.Errorf(codes.Internal, "failed to create storage, error: %+v", err)
 	}
 	return &apiv2pb.CreateStorageResponse{
-		Storage: convertStorageFromStore(storage),
+		Storage: ConvertStorageFromStore(storage),
 	}, nil
 }
 
@@ -39,7 +39,7 @@ func (s *APIV2Service) ListStorages(ctx context.Context, _ *apiv2pb.ListStorages
 		Storages: []*apiv2pb.Storage{},
 	}
 	for _, storage := range storages {
-		response.Storages = append(response.Storages, convertStorageFromStore(storage))
+		response.Storages = append(response.Storages, ConvertStorageFromStore(storage))
 	}
 	return response, nil
 }
@@ -55,7 +55,7 @@ func (s *APIV2Service) GetStorage(ctx context.Context, request *apiv2pb.GetStora
 		return nil, status.Errorf(codes.NotFound, "storage not found")
 	}
 	return &apiv2pb.GetStorageResponse{
-		Storage: convertStorageFromStore(storage),
+		Storage: ConvertStorageFromStore(storage),
 	}, nil
 }
 
@@ -82,7 +82,7 @@ func (s *APIV2Service) UpdateStorage(ctx context.Context, request *apiv2pb.Updat
 		return nil, status.Errorf(codes.Internal, "failed to update storage, error: %+v", err)
 	}
 	return &apiv2pb.UpdateStorageResponse{
-		Storage: convertStorageFromStore(storage),
+		Storage: ConvertStorageFromStore(storage),
 	}, nil
 }
 
@@ -96,7 +96,7 @@ func (s *APIV2Service) DeleteStorage(ctx context.Context, request *apiv2pb.Delet
 	return &apiv2pb.DeleteStorageResponse{}, nil
 }
 
-func convertStorageFromStore(storage *storepb.Storage) *apiv2pb.Storage {
+func ConvertStorageFromStore(storage *storepb.Storage) *apiv2pb.Storage {
 	temp := &apiv2pb.Storage{
 		Id:    storage.Id,
 		Title: storage.Name,

programming_language: Go
filename: server.go
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,6 @@ import (
 	storepb "github.com/usememos/memos/proto/gen/store"
 	"github.com/usememos/memos/server/integration"
 	"github.com/usememos/memos/server/profile"
-	apiv1 "github.com/usememos/memos/server/route/api/v1"
 	apiv2 "github.com/usememos/memos/server/route/api/v2"
 	"github.com/usememos/memos/server/route/frontend"
 	versionchecker "github.com/usememos/memos/server/service/version_checker"
@@ -75,11 +74,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
 		frontendService.Serve(ctx, e)
 	}
 
-	// Register API v1 endpoints.
-	rootGroup := e.Group("")
-	apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store, s.telegramBot)
-	apiV1Service.Register(rootGroup)
-
 	apiV2Service := apiv2.NewAPIV2Service(s.Secret, profile, store, s.Profile.Port+1)
 	// Register gRPC gateway as api v2.
 	if err := apiV2Service.RegisterGateway(ctx, e); err != nil {

programming_language: Go