memo.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package sqlite
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "strings"
  7. "github.com/pkg/errors"
  8. "github.com/usememos/memos/common/util"
  9. "github.com/usememos/memos/store"
  10. )
  11. func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, error) {
  12. fields := []string{"`creator_id`", "`content`", "`visibility`"}
  13. placeholder := []string{"?", "?", "?"}
  14. args := []any{create.CreatorID, create.Content, create.Visibility}
  15. if create.ID != 0 {
  16. fields = append(fields, "`id`")
  17. placeholder = append(placeholder, "?")
  18. args = append(args, create.ID)
  19. }
  20. if create.CreatedTs != 0 {
  21. fields = append(fields, "`created_ts`")
  22. placeholder = append(placeholder, "?")
  23. args = append(args, create.CreatedTs)
  24. }
  25. if create.UpdatedTs != 0 {
  26. fields = append(fields, "`updated_ts`")
  27. placeholder = append(placeholder, "?")
  28. args = append(args, create.UpdatedTs)
  29. }
  30. if create.RowStatus != "" {
  31. fields = append(fields, "`row_status`")
  32. placeholder = append(placeholder, "?")
  33. args = append(args, create.RowStatus)
  34. }
  35. stmt := "INSERT INTO memo (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`, `updated_ts`, `row_status`"
  36. if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
  37. &create.ID,
  38. &create.CreatedTs,
  39. &create.UpdatedTs,
  40. &create.RowStatus,
  41. ); err != nil {
  42. return nil, err
  43. }
  44. return create, nil
  45. }
  46. func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo, error) {
  47. where, args := []string{"1 = 1"}, []any{}
  48. if v := find.ID; v != nil {
  49. where, args = append(where, "memo.id = ?"), append(args, *v)
  50. }
  51. if v := find.CreatorID; v != nil {
  52. where, args = append(where, "memo.creator_id = ?"), append(args, *v)
  53. }
  54. if v := find.RowStatus; v != nil {
  55. where, args = append(where, "memo.row_status = ?"), append(args, *v)
  56. }
  57. if v := find.CreatedTsBefore; v != nil {
  58. where, args = append(where, "memo.created_ts < ?"), append(args, *v)
  59. }
  60. if v := find.CreatedTsAfter; v != nil {
  61. where, args = append(where, "memo.created_ts > ?"), append(args, *v)
  62. }
  63. if v := find.ContentSearch; len(v) != 0 {
  64. for _, s := range v {
  65. where, args = append(where, "memo.content LIKE ?"), append(args, "%"+s+"%")
  66. }
  67. }
  68. if v := find.VisibilityList; len(v) != 0 {
  69. list := []string{}
  70. for _, visibility := range v {
  71. list = append(list, fmt.Sprintf("$%d", len(args)+1))
  72. args = append(args, visibility)
  73. }
  74. where = append(where, fmt.Sprintf("memo.visibility in (%s)", strings.Join(list, ",")))
  75. }
  76. if v := find.Pinned; v != nil {
  77. where = append(where, "memo_organizer.pinned = 1")
  78. }
  79. if v := find.HasParent; v != nil {
  80. if *v {
  81. where = append(where, "parent_id IS NOT NULL")
  82. } else {
  83. where = append(where, "parent_id IS NULL")
  84. }
  85. }
  86. orders := []string{"pinned DESC"}
  87. if find.OrderByUpdatedTs {
  88. orders = append(orders, "updated_ts DESC")
  89. } else {
  90. orders = append(orders, "created_ts DESC")
  91. }
  92. orders = append(orders, "id DESC")
  93. fields := []string{
  94. `memo.id AS id,`,
  95. `memo.creator_id AS creator_id,`,
  96. `memo.created_ts AS created_ts,`,
  97. `memo.updated_ts AS updated_ts,`,
  98. `memo.row_status AS row_status,`,
  99. `memo.visibility AS visibility,`,
  100. }
  101. if !find.ExcludeContent {
  102. fields = append(fields, `memo.content AS content,`)
  103. }
  104. query := `
  105. SELECT
  106. ` + strings.Join(fields, "\n") + `
  107. CASE WHEN mo.pinned = 1 THEN 1 ELSE 0 END AS pinned,
  108. (
  109. SELECT
  110. related_memo_id
  111. FROM
  112. memo_relation
  113. WHERE
  114. memo_relation.memo_id = memo.id AND memo_relation.type = 'COMMENT'
  115. LIMIT 1
  116. ) AS parent_id,
  117. GROUP_CONCAT(resource.id) AS resource_id_list,
  118. (
  119. SELECT
  120. GROUP_CONCAT(memo_relation.memo_id || ':' || memo_relation.related_memo_id || ':' || memo_relation.type)
  121. FROM
  122. memo_relation
  123. WHERE
  124. memo_relation.memo_id = memo.id OR memo_relation.related_memo_id = memo.id
  125. ) AS relation_list
  126. FROM
  127. memo
  128. LEFT JOIN
  129. memo_organizer mo ON memo.id = mo.memo_id
  130. LEFT JOIN
  131. resource ON memo.id = resource.memo_id
  132. WHERE ` + strings.Join(where, " AND ") + `
  133. GROUP BY memo.id
  134. ORDER BY ` + strings.Join(orders, ", ")
  135. if find.Limit != nil {
  136. query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
  137. if find.Offset != nil {
  138. query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
  139. }
  140. }
  141. rows, err := d.db.QueryContext(ctx, query, args...)
  142. if err != nil {
  143. return nil, err
  144. }
  145. defer rows.Close()
  146. list := make([]*store.Memo, 0)
  147. for rows.Next() {
  148. var memo store.Memo
  149. var memoResourceIDList sql.NullString
  150. var memoRelationList sql.NullString
  151. dests := []any{
  152. &memo.ID,
  153. &memo.CreatorID,
  154. &memo.CreatedTs,
  155. &memo.UpdatedTs,
  156. &memo.RowStatus,
  157. &memo.Visibility,
  158. }
  159. if !find.ExcludeContent {
  160. dests = append(dests, &memo.Content)
  161. }
  162. dests = append(dests, &memo.Pinned, &memo.ParentID, &memoResourceIDList, &memoRelationList)
  163. if err := rows.Scan(dests...); err != nil {
  164. return nil, err
  165. }
  166. if memoResourceIDList.Valid {
  167. idStringList := strings.Split(memoResourceIDList.String, ",")
  168. memo.ResourceIDList = make([]int32, 0, len(idStringList))
  169. for _, idString := range idStringList {
  170. id, err := util.ConvertStringToInt32(idString)
  171. if err != nil {
  172. return nil, err
  173. }
  174. memo.ResourceIDList = append(memo.ResourceIDList, id)
  175. }
  176. }
  177. if memoRelationList.Valid {
  178. memo.RelationList = make([]*store.MemoRelation, 0)
  179. relatedMemoTypeList := strings.Split(memoRelationList.String, ",")
  180. for _, relatedMemoType := range relatedMemoTypeList {
  181. relatedMemoTypeList := strings.Split(relatedMemoType, ":")
  182. if len(relatedMemoTypeList) != 3 {
  183. return nil, errors.New("invalid relation format")
  184. }
  185. memoID, err := util.ConvertStringToInt32(relatedMemoTypeList[0])
  186. if err != nil {
  187. return nil, err
  188. }
  189. relatedMemoID, err := util.ConvertStringToInt32(relatedMemoTypeList[1])
  190. if err != nil {
  191. return nil, err
  192. }
  193. relationType := store.MemoRelationType(relatedMemoTypeList[2])
  194. memo.RelationList = append(memo.RelationList, &store.MemoRelation{
  195. MemoID: memoID,
  196. RelatedMemoID: relatedMemoID,
  197. Type: relationType,
  198. })
  199. }
  200. }
  201. list = append(list, &memo)
  202. }
  203. if err := rows.Err(); err != nil {
  204. return nil, err
  205. }
  206. return list, nil
  207. }
  208. func (d *DB) UpdateMemo(ctx context.Context, update *store.UpdateMemo) error {
  209. set, args := []string{}, []any{}
  210. if v := update.CreatedTs; v != nil {
  211. set, args = append(set, "created_ts = ?"), append(args, *v)
  212. }
  213. if v := update.UpdatedTs; v != nil {
  214. set, args = append(set, "updated_ts = ?"), append(args, *v)
  215. }
  216. if v := update.RowStatus; v != nil {
  217. set, args = append(set, "row_status = ?"), append(args, *v)
  218. }
  219. if v := update.Content; v != nil {
  220. set, args = append(set, "content = ?"), append(args, *v)
  221. }
  222. if v := update.Visibility; v != nil {
  223. set, args = append(set, "visibility = ?"), append(args, *v)
  224. }
  225. args = append(args, update.ID)
  226. stmt := `
  227. UPDATE memo
  228. SET ` + strings.Join(set, ", ") + `
  229. WHERE id = ?
  230. `
  231. if _, err := d.db.ExecContext(ctx, stmt, args...); err != nil {
  232. return err
  233. }
  234. return nil
  235. }
  236. func (d *DB) DeleteMemo(ctx context.Context, delete *store.DeleteMemo) error {
  237. where, args := []string{"id = ?"}, []any{delete.ID}
  238. stmt := `DELETE FROM memo WHERE ` + strings.Join(where, " AND ")
  239. result, err := d.db.ExecContext(ctx, stmt, args...)
  240. if err != nil {
  241. return err
  242. }
  243. if _, err := result.RowsAffected(); err != nil {
  244. return err
  245. }
  246. if err := d.Vacuum(ctx); err != nil {
  247. // Prevent linter warning.
  248. return err
  249. }
  250. return nil
  251. }
  252. func (d *DB) FindMemosVisibilityList(ctx context.Context, memoIDs []int32) ([]store.Visibility, error) {
  253. args := make([]any, 0, len(memoIDs))
  254. list := make([]string, 0, len(memoIDs))
  255. for _, memoID := range memoIDs {
  256. args = append(args, memoID)
  257. list = append(list, "?")
  258. }
  259. where := fmt.Sprintf("id in (%s)", strings.Join(list, ","))
  260. query := `SELECT DISTINCT(visibility) FROM memo WHERE ` + where
  261. rows, err := d.db.QueryContext(ctx, query, args...)
  262. if err != nil {
  263. return nil, err
  264. }
  265. defer rows.Close()
  266. visibilityList := make([]store.Visibility, 0)
  267. for rows.Next() {
  268. var visibility store.Visibility
  269. if err := rows.Scan(&visibility); err != nil {
  270. return nil, err
  271. }
  272. visibilityList = append(visibilityList, visibility)
  273. }
  274. if err := rows.Err(); err != nil {
  275. return nil, err
  276. }
  277. return visibilityList, nil
  278. }
  279. func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
  280. stmt := `
  281. DELETE FROM
  282. memo
  283. WHERE
  284. creator_id NOT IN (
  285. SELECT
  286. id
  287. FROM
  288. user
  289. )`
  290. _, err := tx.ExecContext(ctx, stmt)
  291. if err != nil {
  292. return err
  293. }
  294. return nil
  295. }