memo.go 8.1 KB

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