system_setting.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. package v1
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "github.com/labstack/echo/v4"
  9. "github.com/usememos/memos/store"
  10. )
  11. type SystemSettingName string
  12. const (
  13. // SystemSettingServerIDName is the name of server id.
  14. SystemSettingServerIDName SystemSettingName = "server-id"
  15. // SystemSettingSecretSessionName is the name of secret session.
  16. SystemSettingSecretSessionName SystemSettingName = "secret-session"
  17. // SystemSettingAllowSignUpName is the name of allow signup setting.
  18. SystemSettingAllowSignUpName SystemSettingName = "allow-signup"
  19. // SystemSettingDisablePublicMemosName is the name of disable public memos setting.
  20. SystemSettingDisablePublicMemosName SystemSettingName = "disable-public-memos"
  21. // SystemSettingMaxUploadSizeMiBName is the name of max upload size setting.
  22. SystemSettingMaxUploadSizeMiBName SystemSettingName = "max-upload-size-mib"
  23. // SystemSettingAdditionalStyleName is the name of additional style.
  24. SystemSettingAdditionalStyleName SystemSettingName = "additional-style"
  25. // SystemSettingAdditionalScriptName is the name of additional script.
  26. SystemSettingAdditionalScriptName SystemSettingName = "additional-script"
  27. // SystemSettingCustomizedProfileName is the name of customized server profile.
  28. SystemSettingCustomizedProfileName SystemSettingName = "customized-profile"
  29. // SystemSettingStorageServiceIDName is the name of storage service ID.
  30. SystemSettingStorageServiceIDName SystemSettingName = "storage-service-id"
  31. // SystemSettingLocalStoragePathName is the name of local storage path.
  32. SystemSettingLocalStoragePathName SystemSettingName = "local-storage-path"
  33. // SystemSettingTelegramBotToken is the name of Telegram Bot Token.
  34. SystemSettingTelegramBotTokenName SystemSettingName = "telegram-bot-token"
  35. // SystemSettingMemoDisplayWithUpdatedTsName is the name of memo display with updated ts.
  36. SystemSettingMemoDisplayWithUpdatedTsName SystemSettingName = "memo-display-with-updated-ts"
  37. // SystemSettingOpenAIConfigName is the name of OpenAI config.
  38. SystemSettingOpenAIConfigName SystemSettingName = "openai-config"
  39. // SystemSettingAutoBackupIntervalName is the name of auto backup interval as seconds.
  40. SystemSettingAutoBackupIntervalName SystemSettingName = "auto-backup-interval"
  41. )
  42. // CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
  43. type CustomizedProfile struct {
  44. // Name is the server name, default is `memos`
  45. Name string `json:"name"`
  46. // LogoURL is the url of logo image.
  47. LogoURL string `json:"logoUrl"`
  48. // Description is the server description.
  49. Description string `json:"description"`
  50. // Locale is the server default locale.
  51. Locale string `json:"locale"`
  52. // Appearance is the server default appearance.
  53. Appearance string `json:"appearance"`
  54. // ExternalURL is the external url of server. e.g. https://usermemos.com
  55. ExternalURL string `json:"externalUrl"`
  56. }
  57. func (key SystemSettingName) String() string {
  58. return string(key)
  59. }
  60. type SystemSetting struct {
  61. Name SystemSettingName `json:"name"`
  62. // Value is a JSON string with basic value.
  63. Value string `json:"value"`
  64. Description string `json:"description"`
  65. }
  66. type OpenAIConfig struct {
  67. Key string `json:"key"`
  68. Host string `json:"host"`
  69. }
  70. type UpsertSystemSettingRequest struct {
  71. Name SystemSettingName `json:"name"`
  72. Value string `json:"value"`
  73. Description string `json:"description"`
  74. }
  75. const systemSettingUnmarshalError = `failed to unmarshal value from system setting "%v"`
  76. func (upsert UpsertSystemSettingRequest) Validate() error {
  77. switch settingName := upsert.Name; settingName {
  78. case SystemSettingServerIDName:
  79. return fmt.Errorf("updating %v is not allowed", settingName)
  80. case SystemSettingAllowSignUpName:
  81. var value bool
  82. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  83. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  84. }
  85. case SystemSettingDisablePublicMemosName:
  86. var value bool
  87. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  88. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  89. }
  90. case SystemSettingMaxUploadSizeMiBName:
  91. var value int
  92. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  93. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  94. }
  95. case SystemSettingAdditionalStyleName:
  96. var value string
  97. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  98. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  99. }
  100. case SystemSettingAdditionalScriptName:
  101. var value string
  102. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  103. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  104. }
  105. case SystemSettingCustomizedProfileName:
  106. customizedProfile := CustomizedProfile{
  107. Name: "memos",
  108. LogoURL: "",
  109. Description: "",
  110. Locale: "en",
  111. Appearance: "system",
  112. ExternalURL: "",
  113. }
  114. if err := json.Unmarshal([]byte(upsert.Value), &customizedProfile); err != nil {
  115. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  116. }
  117. case SystemSettingStorageServiceIDName:
  118. // Note: 0 is the default value(database) for storage service ID.
  119. value := 0
  120. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  121. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  122. }
  123. return nil
  124. case SystemSettingLocalStoragePathName:
  125. value := ""
  126. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  127. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  128. }
  129. case SystemSettingOpenAIConfigName:
  130. value := OpenAIConfig{}
  131. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  132. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  133. }
  134. case SystemSettingAutoBackupIntervalName:
  135. var value string
  136. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  137. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  138. }
  139. if value != "" {
  140. v, err := strconv.Atoi(value)
  141. if err != nil {
  142. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  143. }
  144. if v < 0 {
  145. return fmt.Errorf("backup interval should > 0")
  146. }
  147. }
  148. case SystemSettingTelegramBotTokenName:
  149. if upsert.Value == "" {
  150. return nil
  151. }
  152. // Bot Token with Reverse Proxy shoule like `http.../bot<token>`
  153. if strings.HasPrefix(upsert.Value, "http") {
  154. slashIndex := strings.LastIndexAny(upsert.Value, "/")
  155. if strings.HasPrefix(upsert.Value[slashIndex:], "/bot") {
  156. return nil
  157. }
  158. return fmt.Errorf("token start with `http` must end with `/bot<token>`")
  159. }
  160. fragments := strings.Split(upsert.Value, ":")
  161. if len(fragments) != 2 {
  162. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  163. }
  164. case SystemSettingMemoDisplayWithUpdatedTsName:
  165. var value bool
  166. if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
  167. return fmt.Errorf(systemSettingUnmarshalError, settingName)
  168. }
  169. default:
  170. return fmt.Errorf("invalid system setting name")
  171. }
  172. return nil
  173. }
  174. func (s *APIV1Service) registerSystemSettingRoutes(g *echo.Group) {
  175. g.POST("/system/setting", func(c echo.Context) error {
  176. ctx := c.Request().Context()
  177. userID, ok := c.Get(getUserIDContextKey()).(int)
  178. if !ok {
  179. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  180. }
  181. user, err := s.Store.GetUser(ctx, &store.FindUser{
  182. ID: &userID,
  183. })
  184. if err != nil {
  185. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  186. }
  187. if user == nil || user.Role != store.RoleHost {
  188. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  189. }
  190. systemSettingUpsert := &UpsertSystemSettingRequest{}
  191. if err := json.NewDecoder(c.Request().Body).Decode(systemSettingUpsert); err != nil {
  192. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post system setting request").SetInternal(err)
  193. }
  194. if err := systemSettingUpsert.Validate(); err != nil {
  195. return echo.NewHTTPError(http.StatusBadRequest, "invalid system setting").SetInternal(err)
  196. }
  197. systemSetting, err := s.Store.UpsertSystemSetting(ctx, &store.SystemSetting{
  198. Name: systemSettingUpsert.Name.String(),
  199. Value: systemSettingUpsert.Value,
  200. Description: systemSettingUpsert.Description,
  201. })
  202. if err != nil {
  203. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err)
  204. }
  205. return c.JSON(http.StatusOK, convertSystemSettingFromStore(systemSetting))
  206. })
  207. g.GET("/system/setting", func(c echo.Context) error {
  208. ctx := c.Request().Context()
  209. userID, ok := c.Get(getUserIDContextKey()).(int)
  210. if !ok {
  211. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  212. }
  213. user, err := s.Store.GetUser(ctx, &store.FindUser{
  214. ID: &userID,
  215. })
  216. if err != nil {
  217. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  218. }
  219. if user == nil || user.Role != store.RoleHost {
  220. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  221. }
  222. list, err := s.Store.ListSystemSettings(ctx, &store.FindSystemSetting{})
  223. if err != nil {
  224. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
  225. }
  226. systemSettingList := make([]*SystemSetting, 0, len(list))
  227. for _, systemSetting := range list {
  228. systemSettingList = append(systemSettingList, convertSystemSettingFromStore(systemSetting))
  229. }
  230. return c.JSON(http.StatusOK, systemSettingList)
  231. })
  232. }
  233. func convertSystemSettingFromStore(systemSetting *store.SystemSetting) *SystemSetting {
  234. return &SystemSetting{
  235. Name: SystemSettingName(systemSetting.Name),
  236. Value: systemSetting.Value,
  237. Description: systemSetting.Description,
  238. }
  239. }