memo.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. package store
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "strings"
  7. "github.com/usememos/memos/api"
  8. "github.com/usememos/memos/common"
  9. )
  10. // memoRaw is the store model for an Memo.
  11. // Fields have exactly the same meanings as Memo.
  12. type memoRaw struct {
  13. ID int
  14. // Standard fields
  15. RowStatus api.RowStatus
  16. CreatorID int
  17. CreatedTs int64
  18. UpdatedTs int64
  19. // Domain specific fields
  20. Content string
  21. Visibility api.Visibility
  22. Pinned bool
  23. }
  24. // toMemo creates an instance of Memo based on the memoRaw.
  25. // This is intended to be called when we need to compose an Memo relationship.
  26. func (raw *memoRaw) toMemo() *api.Memo {
  27. return &api.Memo{
  28. ID: raw.ID,
  29. // Standard fields
  30. RowStatus: raw.RowStatus,
  31. CreatorID: raw.CreatorID,
  32. CreatedTs: raw.CreatedTs,
  33. UpdatedTs: raw.UpdatedTs,
  34. // Domain specific fields
  35. Content: raw.Content,
  36. Visibility: raw.Visibility,
  37. Pinned: raw.Pinned,
  38. }
  39. }
  40. func (s *Store) ComposeMemo(ctx context.Context, memo *api.Memo) (*api.Memo, error) {
  41. if err := s.ComposeMemoCreator(ctx, memo); err != nil {
  42. return nil, err
  43. }
  44. if err := s.ComposeMemoResourceList(ctx, memo); err != nil {
  45. return nil, err
  46. }
  47. return memo, nil
  48. }
  49. func (s *Store) CreateMemo(ctx context.Context, create *api.MemoCreate) (*api.Memo, error) {
  50. tx, err := s.db.BeginTx(ctx, nil)
  51. if err != nil {
  52. return nil, FormatError(err)
  53. }
  54. defer tx.Rollback()
  55. memoRaw, err := createMemoRaw(ctx, tx, create)
  56. if err != nil {
  57. return nil, err
  58. }
  59. if err := tx.Commit(); err != nil {
  60. return nil, FormatError(err)
  61. }
  62. s.memoCache.Store(memoRaw.ID, memoRaw)
  63. memo, err := s.ComposeMemo(ctx, memoRaw.toMemo())
  64. if err != nil {
  65. return nil, err
  66. }
  67. return memo, nil
  68. }
  69. func (s *Store) PatchMemo(ctx context.Context, patch *api.MemoPatch) (*api.Memo, error) {
  70. tx, err := s.db.BeginTx(ctx, nil)
  71. if err != nil {
  72. return nil, FormatError(err)
  73. }
  74. defer tx.Rollback()
  75. memoRaw, err := patchMemoRaw(ctx, tx, patch)
  76. if err != nil {
  77. return nil, err
  78. }
  79. if err := tx.Commit(); err != nil {
  80. return nil, FormatError(err)
  81. }
  82. s.memoCache.Store(memoRaw.ID, memoRaw)
  83. memo, err := s.ComposeMemo(ctx, memoRaw.toMemo())
  84. if err != nil {
  85. return nil, err
  86. }
  87. return memo, nil
  88. }
  89. func (s *Store) FindMemoList(ctx context.Context, find *api.MemoFind) ([]*api.Memo, error) {
  90. tx, err := s.db.BeginTx(ctx, nil)
  91. if err != nil {
  92. return nil, FormatError(err)
  93. }
  94. defer tx.Rollback()
  95. memoRawList, err := findMemoRawList(ctx, tx, find)
  96. if err != nil {
  97. return nil, err
  98. }
  99. list := []*api.Memo{}
  100. for _, raw := range memoRawList {
  101. memo, err := s.ComposeMemo(ctx, raw.toMemo())
  102. if err != nil {
  103. return nil, err
  104. }
  105. list = append(list, memo)
  106. }
  107. return list, nil
  108. }
  109. func (s *Store) FindMemo(ctx context.Context, find *api.MemoFind) (*api.Memo, error) {
  110. if find.ID != nil {
  111. if memo, ok := s.memoCache.Load(*find.ID); ok {
  112. memoRaw := memo.(*memoRaw)
  113. memo, err := s.ComposeMemo(ctx, memoRaw.toMemo())
  114. if err != nil {
  115. return nil, err
  116. }
  117. return memo, nil
  118. }
  119. }
  120. tx, err := s.db.BeginTx(ctx, nil)
  121. if err != nil {
  122. return nil, FormatError(err)
  123. }
  124. defer tx.Rollback()
  125. list, err := findMemoRawList(ctx, tx, find)
  126. if err != nil {
  127. return nil, err
  128. }
  129. if len(list) == 0 {
  130. return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
  131. }
  132. memoRaw := list[0]
  133. s.memoCache.Store(memoRaw.ID, memoRaw)
  134. memo, err := s.ComposeMemo(ctx, memoRaw.toMemo())
  135. if err != nil {
  136. return nil, err
  137. }
  138. return memo, nil
  139. }
  140. func (s *Store) DeleteMemo(ctx context.Context, delete *api.MemoDelete) error {
  141. tx, err := s.db.BeginTx(ctx, nil)
  142. if err != nil {
  143. return FormatError(err)
  144. }
  145. defer tx.Rollback()
  146. if err := deleteMemo(ctx, tx, delete); err != nil {
  147. return FormatError(err)
  148. }
  149. if err := vacuum(ctx, tx); err != nil {
  150. return err
  151. }
  152. if err := tx.Commit(); err != nil {
  153. return FormatError(err)
  154. }
  155. s.memoCache.Delete(delete.ID)
  156. return nil
  157. }
  158. func createMemoRaw(ctx context.Context, tx *sql.Tx, create *api.MemoCreate) (*memoRaw, error) {
  159. set := []string{"creator_id", "content", "visibility"}
  160. args := []any{create.CreatorID, create.Content, create.Visibility}
  161. placeholder := []string{"?", "?", "?"}
  162. if v := create.CreatedTs; v != nil {
  163. set, args, placeholder = append(set, "created_ts"), append(args, *v), append(placeholder, "?")
  164. }
  165. query := `
  166. INSERT INTO memo (
  167. ` + strings.Join(set, ", ") + `
  168. )
  169. VALUES (` + strings.Join(placeholder, ",") + `)
  170. RETURNING id, creator_id, created_ts, updated_ts, row_status, content, visibility
  171. `
  172. var memoRaw memoRaw
  173. if err := tx.QueryRowContext(ctx, query, args...).Scan(
  174. &memoRaw.ID,
  175. &memoRaw.CreatorID,
  176. &memoRaw.CreatedTs,
  177. &memoRaw.UpdatedTs,
  178. &memoRaw.RowStatus,
  179. &memoRaw.Content,
  180. &memoRaw.Visibility,
  181. ); err != nil {
  182. return nil, FormatError(err)
  183. }
  184. return &memoRaw, nil
  185. }
  186. func patchMemoRaw(ctx context.Context, tx *sql.Tx, patch *api.MemoPatch) (*memoRaw, error) {
  187. set, args := []string{}, []any{}
  188. if v := patch.CreatedTs; v != nil {
  189. set, args = append(set, "created_ts = ?"), append(args, *v)
  190. }
  191. if v := patch.UpdatedTs; v != nil {
  192. set, args = append(set, "updated_ts = ?"), append(args, *v)
  193. }
  194. if v := patch.RowStatus; v != nil {
  195. set, args = append(set, "row_status = ?"), append(args, *v)
  196. }
  197. if v := patch.Content; v != nil {
  198. set, args = append(set, "content = ?"), append(args, *v)
  199. }
  200. if v := patch.Visibility; v != nil {
  201. set, args = append(set, "visibility = ?"), append(args, *v)
  202. }
  203. args = append(args, patch.ID)
  204. query := `
  205. UPDATE memo
  206. SET ` + strings.Join(set, ", ") + `
  207. WHERE id = ?
  208. RETURNING id, creator_id, created_ts, updated_ts, row_status, content, visibility
  209. `
  210. var memoRaw memoRaw
  211. if err := tx.QueryRowContext(ctx, query, args...).Scan(
  212. &memoRaw.ID,
  213. &memoRaw.CreatorID,
  214. &memoRaw.CreatedTs,
  215. &memoRaw.UpdatedTs,
  216. &memoRaw.RowStatus,
  217. &memoRaw.Content,
  218. &memoRaw.Visibility,
  219. ); err != nil {
  220. return nil, FormatError(err)
  221. }
  222. return &memoRaw, nil
  223. }
  224. func findMemoRawList(ctx context.Context, tx *sql.Tx, find *api.MemoFind) ([]*memoRaw, error) {
  225. where, args := []string{"1 = 1"}, []any{}
  226. if v := find.ID; v != nil {
  227. where, args = append(where, "memo.id = ?"), append(args, *v)
  228. }
  229. if v := find.CreatorID; v != nil {
  230. where, args = append(where, "memo.creator_id = ?"), append(args, *v)
  231. }
  232. if v := find.RowStatus; v != nil {
  233. where, args = append(where, "memo.row_status = ?"), append(args, *v)
  234. }
  235. if v := find.Pinned; v != nil {
  236. where = append(where, "memo_organizer.pinned = 1")
  237. }
  238. if v := find.ContentSearch; v != nil {
  239. where, args = append(where, "memo.content LIKE ?"), append(args, "%"+*v+"%")
  240. }
  241. if v := find.VisibilityList; len(v) != 0 {
  242. list := []string{}
  243. for _, visibility := range v {
  244. list = append(list, fmt.Sprintf("$%d", len(args)+1))
  245. args = append(args, visibility)
  246. }
  247. where = append(where, fmt.Sprintf("memo.visibility in (%s)", strings.Join(list, ",")))
  248. }
  249. query := `
  250. SELECT
  251. memo.id,
  252. memo.creator_id,
  253. memo.created_ts,
  254. memo.updated_ts,
  255. memo.row_status,
  256. memo.content,
  257. memo.visibility,
  258. IFNULL(memo_organizer.pinned, 0) AS pinned
  259. FROM memo
  260. LEFT JOIN memo_organizer ON memo_organizer.memo_id = memo.id AND memo_organizer.user_id = memo.creator_id
  261. WHERE ` + strings.Join(where, " AND ") + `
  262. ORDER BY pinned DESC, memo.created_ts DESC
  263. `
  264. if find.Limit != nil {
  265. query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
  266. if find.Offset != nil {
  267. query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
  268. }
  269. }
  270. rows, err := tx.QueryContext(ctx, query, args...)
  271. if err != nil {
  272. return nil, FormatError(err)
  273. }
  274. defer rows.Close()
  275. memoRawList := make([]*memoRaw, 0)
  276. for rows.Next() {
  277. var memoRaw memoRaw
  278. var pinned sql.NullBool
  279. if err := rows.Scan(
  280. &memoRaw.ID,
  281. &memoRaw.CreatorID,
  282. &memoRaw.CreatedTs,
  283. &memoRaw.UpdatedTs,
  284. &memoRaw.RowStatus,
  285. &memoRaw.Content,
  286. &memoRaw.Visibility,
  287. &pinned,
  288. ); err != nil {
  289. return nil, FormatError(err)
  290. }
  291. if pinned.Valid {
  292. memoRaw.Pinned = pinned.Bool
  293. }
  294. memoRawList = append(memoRawList, &memoRaw)
  295. }
  296. if err := rows.Err(); err != nil {
  297. return nil, FormatError(err)
  298. }
  299. return memoRawList, nil
  300. }
  301. func deleteMemo(ctx context.Context, tx *sql.Tx, delete *api.MemoDelete) error {
  302. where, args := []string{"id = ?"}, []any{delete.ID}
  303. stmt := `DELETE FROM memo WHERE ` + strings.Join(where, " AND ")
  304. result, err := tx.ExecContext(ctx, stmt, args...)
  305. if err != nil {
  306. return FormatError(err)
  307. }
  308. rows, _ := result.RowsAffected()
  309. if rows == 0 {
  310. return &common.Error{Code: common.NotFound, Err: fmt.Errorf("memo not found")}
  311. }
  312. return nil
  313. }
  314. func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
  315. stmt := `
  316. DELETE FROM
  317. memo
  318. WHERE
  319. creator_id NOT IN (
  320. SELECT
  321. id
  322. FROM
  323. user
  324. )`
  325. _, err := tx.ExecContext(ctx, stmt)
  326. if err != nil {
  327. return FormatError(err)
  328. }
  329. return nil
  330. }