storage.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package v1
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "github.com/labstack/echo/v4"
  7. "github.com/usememos/memos/api/auth"
  8. "github.com/usememos/memos/common/util"
  9. "github.com/usememos/memos/store"
  10. )
  11. const (
  12. // LocalStorage means the storage service is local file system.
  13. LocalStorage int32 = -1
  14. // DatabaseStorage means the storage service is database.
  15. DatabaseStorage int32 = 0
  16. )
  17. type StorageType string
  18. const (
  19. StorageS3 StorageType = "S3"
  20. )
  21. func (t StorageType) String() string {
  22. return string(t)
  23. }
  24. type StorageConfig struct {
  25. S3Config *StorageS3Config `json:"s3Config"`
  26. }
  27. type StorageS3Config struct {
  28. EndPoint string `json:"endPoint"`
  29. Path string `json:"path"`
  30. Region string `json:"region"`
  31. AccessKey string `json:"accessKey"`
  32. SecretKey string `json:"secretKey"`
  33. Bucket string `json:"bucket"`
  34. URLPrefix string `json:"urlPrefix"`
  35. URLSuffix string `json:"urlSuffix"`
  36. }
  37. type Storage struct {
  38. ID int32 `json:"id"`
  39. Name string `json:"name"`
  40. Type StorageType `json:"type"`
  41. Config *StorageConfig `json:"config"`
  42. }
  43. type CreateStorageRequest struct {
  44. Name string `json:"name"`
  45. Type StorageType `json:"type"`
  46. Config *StorageConfig `json:"config"`
  47. }
  48. type UpdateStorageRequest struct {
  49. Type StorageType `json:"type"`
  50. Name *string `json:"name"`
  51. Config *StorageConfig `json:"config"`
  52. }
  53. func (s *APIV1Service) registerStorageRoutes(g *echo.Group) {
  54. g.POST("/storage", func(c echo.Context) error {
  55. ctx := c.Request().Context()
  56. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  57. if !ok {
  58. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  59. }
  60. user, err := s.Store.GetUser(ctx, &store.FindUser{
  61. ID: &userID,
  62. })
  63. if err != nil {
  64. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  65. }
  66. if user == nil || user.Role != store.RoleHost {
  67. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  68. }
  69. create := &CreateStorageRequest{}
  70. if err := json.NewDecoder(c.Request().Body).Decode(create); err != nil {
  71. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
  72. }
  73. configString := ""
  74. if create.Type == StorageS3 && create.Config.S3Config != nil {
  75. configBytes, err := json.Marshal(create.Config.S3Config)
  76. if err != nil {
  77. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
  78. }
  79. configString = string(configBytes)
  80. }
  81. storage, err := s.Store.CreateStorage(ctx, &store.Storage{
  82. Name: create.Name,
  83. Type: create.Type.String(),
  84. Config: configString,
  85. })
  86. if err != nil {
  87. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create storage").SetInternal(err)
  88. }
  89. storageMessage, err := ConvertStorageFromStore(storage)
  90. if err != nil {
  91. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
  92. }
  93. return c.JSON(http.StatusOK, storageMessage)
  94. })
  95. g.PATCH("/storage/:storageId", func(c echo.Context) error {
  96. ctx := c.Request().Context()
  97. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  98. if !ok {
  99. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  100. }
  101. user, err := s.Store.GetUser(ctx, &store.FindUser{
  102. ID: &userID,
  103. })
  104. if err != nil {
  105. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  106. }
  107. if user == nil || user.Role != store.RoleHost {
  108. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  109. }
  110. storageID, err := util.ConvertStringToInt32(c.Param("storageId"))
  111. if err != nil {
  112. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("storageId"))).SetInternal(err)
  113. }
  114. update := &UpdateStorageRequest{}
  115. if err := json.NewDecoder(c.Request().Body).Decode(update); err != nil {
  116. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch storage request").SetInternal(err)
  117. }
  118. storageUpdate := &store.UpdateStorage{
  119. ID: storageID,
  120. }
  121. if update.Name != nil {
  122. storageUpdate.Name = update.Name
  123. }
  124. if update.Config != nil {
  125. if update.Type == StorageS3 {
  126. configBytes, err := json.Marshal(update.Config.S3Config)
  127. if err != nil {
  128. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
  129. }
  130. configString := string(configBytes)
  131. storageUpdate.Config = &configString
  132. }
  133. }
  134. storage, err := s.Store.UpdateStorage(ctx, storageUpdate)
  135. if err != nil {
  136. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch storage").SetInternal(err)
  137. }
  138. storageMessage, err := ConvertStorageFromStore(storage)
  139. if err != nil {
  140. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
  141. }
  142. return c.JSON(http.StatusOK, storageMessage)
  143. })
  144. g.GET("/storage", func(c echo.Context) error {
  145. ctx := c.Request().Context()
  146. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  147. if !ok {
  148. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  149. }
  150. user, err := s.Store.GetUser(ctx, &store.FindUser{
  151. ID: &userID,
  152. })
  153. if err != nil {
  154. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  155. }
  156. // We should only show storage list to host user.
  157. if user == nil || user.Role != store.RoleHost {
  158. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  159. }
  160. list, err := s.Store.ListStorages(ctx, &store.FindStorage{})
  161. if err != nil {
  162. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage list").SetInternal(err)
  163. }
  164. storageList := []*Storage{}
  165. for _, storage := range list {
  166. storageMessage, err := ConvertStorageFromStore(storage)
  167. if err != nil {
  168. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
  169. }
  170. storageList = append(storageList, storageMessage)
  171. }
  172. return c.JSON(http.StatusOK, storageList)
  173. })
  174. g.DELETE("/storage/:storageId", func(c echo.Context) error {
  175. ctx := c.Request().Context()
  176. userID, ok := c.Get(auth.UserIDContextKey).(int32)
  177. if !ok {
  178. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  179. }
  180. user, err := s.Store.GetUser(ctx, &store.FindUser{
  181. ID: &userID,
  182. })
  183. if err != nil {
  184. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
  185. }
  186. if user == nil || user.Role != store.RoleHost {
  187. return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
  188. }
  189. storageID, err := util.ConvertStringToInt32(c.Param("storageId"))
  190. if err != nil {
  191. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("storageId"))).SetInternal(err)
  192. }
  193. systemSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{Name: SystemSettingStorageServiceIDName.String()})
  194. if err != nil {
  195. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
  196. }
  197. if systemSetting != nil {
  198. storageServiceID := DatabaseStorage
  199. err = json.Unmarshal([]byte(systemSetting.Value), &storageServiceID)
  200. if err != nil {
  201. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal storage service id").SetInternal(err)
  202. }
  203. if storageServiceID == storageID {
  204. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Storage service %d is using", storageID))
  205. }
  206. }
  207. if err = s.Store.DeleteStorage(ctx, &store.DeleteStorage{ID: storageID}); err != nil {
  208. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete storage").SetInternal(err)
  209. }
  210. return c.JSON(http.StatusOK, true)
  211. })
  212. }
  213. func ConvertStorageFromStore(storage *store.Storage) (*Storage, error) {
  214. storageMessage := &Storage{
  215. ID: storage.ID,
  216. Name: storage.Name,
  217. Type: StorageType(storage.Type),
  218. Config: &StorageConfig{},
  219. }
  220. if storageMessage.Type == StorageS3 {
  221. s3Config := &StorageS3Config{}
  222. if err := json.Unmarshal([]byte(storage.Config), s3Config); err != nil {
  223. return nil, err
  224. }
  225. storageMessage.Config = &StorageConfig{
  226. S3Config: s3Config,
  227. }
  228. }
  229. return storageMessage, nil
  230. }