resource.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package store
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. )
  9. type Resource struct {
  10. ID int32
  11. // Standard fields
  12. CreatorID int32
  13. CreatedTs int64
  14. UpdatedTs int64
  15. // Domain specific fields
  16. Filename string
  17. Blob []byte
  18. InternalPath string
  19. ExternalLink string
  20. Type string
  21. Size int64
  22. // Related fields
  23. RelatedMemoID *int32
  24. }
  25. type FindResource struct {
  26. GetBlob bool
  27. ID *int32
  28. CreatorID *int32
  29. Filename *string
  30. MemoID *int32
  31. HasRelatedMemo bool
  32. Limit *int
  33. Offset *int
  34. }
  35. type UpdateResource struct {
  36. ID int32
  37. UpdatedTs *int64
  38. Filename *string
  39. InternalPath *string
  40. Blob []byte
  41. }
  42. type DeleteResource struct {
  43. ID int32
  44. }
  45. func (s *Store) CreateResource(ctx context.Context, create *Resource) (*Resource, error) {
  46. stmt := `
  47. INSERT INTO resource (
  48. filename,
  49. blob,
  50. external_link,
  51. type,
  52. size,
  53. creator_id,
  54. internal_path
  55. )
  56. VALUES (?, ?, ?, ?, ?, ?, ?)
  57. RETURNING id, created_ts, updated_ts
  58. `
  59. if err := s.db.QueryRowContext(
  60. ctx,
  61. stmt,
  62. create.Filename,
  63. create.Blob,
  64. create.ExternalLink,
  65. create.Type,
  66. create.Size,
  67. create.CreatorID,
  68. create.InternalPath,
  69. ).Scan(&create.ID, &create.CreatedTs, &create.UpdatedTs); err != nil {
  70. return nil, err
  71. }
  72. resource := create
  73. return resource, nil
  74. }
  75. func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resource, error) {
  76. where, args := []string{"1 = 1"}, []any{}
  77. if v := find.ID; v != nil {
  78. where, args = append(where, "resource.id = ?"), append(args, *v)
  79. }
  80. if v := find.CreatorID; v != nil {
  81. where, args = append(where, "resource.creator_id = ?"), append(args, *v)
  82. }
  83. if v := find.Filename; v != nil {
  84. where, args = append(where, "resource.filename = ?"), append(args, *v)
  85. }
  86. if v := find.MemoID; v != nil {
  87. where, args = append(where, "resource.id in (SELECT resource_id FROM memo_resource WHERE memo_id = ?)"), append(args, *v)
  88. }
  89. if find.HasRelatedMemo {
  90. where = append(where, "memo_resource.memo_id IS NOT NULL")
  91. }
  92. fields := []string{"resource.id", "resource.filename", "resource.external_link", "resource.type", "resource.size", "resource.creator_id", "resource.created_ts", "resource.updated_ts", "internal_path"}
  93. if find.GetBlob {
  94. fields = append(fields, "resource.blob")
  95. }
  96. query := fmt.Sprintf(`
  97. SELECT
  98. GROUP_CONCAT(memo_resource.memo_id) as related_memo_ids,
  99. %s
  100. FROM resource
  101. LEFT JOIN memo_resource ON resource.id = memo_resource.resource_id
  102. WHERE %s
  103. GROUP BY resource.id
  104. ORDER BY resource.created_ts DESC
  105. `, strings.Join(fields, ", "), strings.Join(where, " AND "))
  106. if find.Limit != nil {
  107. query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
  108. if find.Offset != nil {
  109. query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset)
  110. }
  111. }
  112. rows, err := s.db.QueryContext(ctx, query, args...)
  113. if err != nil {
  114. return nil, err
  115. }
  116. defer rows.Close()
  117. list := make([]*Resource, 0)
  118. for rows.Next() {
  119. resource := Resource{}
  120. var relatedMemoIDs sql.NullString
  121. dests := []any{
  122. &relatedMemoIDs,
  123. &resource.ID,
  124. &resource.Filename,
  125. &resource.ExternalLink,
  126. &resource.Type,
  127. &resource.Size,
  128. &resource.CreatorID,
  129. &resource.CreatedTs,
  130. &resource.UpdatedTs,
  131. &resource.InternalPath,
  132. }
  133. if find.GetBlob {
  134. dests = append(dests, &resource.Blob)
  135. }
  136. if err := rows.Scan(dests...); err != nil {
  137. return nil, err
  138. }
  139. if relatedMemoIDs.Valid {
  140. relatedMemoIDList := strings.Split(relatedMemoIDs.String, ",")
  141. if len(relatedMemoIDList) > 0 {
  142. // Only take the first related memo ID.
  143. relatedMemoIDInt, err := strconv.ParseInt(relatedMemoIDList[0], 10, 32)
  144. if err != nil {
  145. return nil, err
  146. }
  147. relatedMemoID := int32(relatedMemoIDInt)
  148. resource.RelatedMemoID = &relatedMemoID
  149. }
  150. }
  151. list = append(list, &resource)
  152. }
  153. if err := rows.Err(); err != nil {
  154. return nil, err
  155. }
  156. return list, nil
  157. }
  158. func (s *Store) GetResource(ctx context.Context, find *FindResource) (*Resource, error) {
  159. resources, err := s.ListResources(ctx, find)
  160. if err != nil {
  161. return nil, err
  162. }
  163. if len(resources) == 0 {
  164. return nil, nil
  165. }
  166. return resources[0], nil
  167. }
  168. func (s *Store) UpdateResource(ctx context.Context, update *UpdateResource) (*Resource, error) {
  169. set, args := []string{}, []any{}
  170. if v := update.UpdatedTs; v != nil {
  171. set, args = append(set, "updated_ts = ?"), append(args, *v)
  172. }
  173. if v := update.Filename; v != nil {
  174. set, args = append(set, "filename = ?"), append(args, *v)
  175. }
  176. if v := update.InternalPath; v != nil {
  177. set, args = append(set, "internal_path = ?"), append(args, *v)
  178. }
  179. if v := update.Blob; v != nil {
  180. set, args = append(set, "blob = ?"), append(args, v)
  181. }
  182. args = append(args, update.ID)
  183. fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path"}
  184. stmt := `
  185. UPDATE resource
  186. SET ` + strings.Join(set, ", ") + `
  187. WHERE id = ?
  188. RETURNING ` + strings.Join(fields, ", ")
  189. resource := Resource{}
  190. dests := []any{
  191. &resource.ID,
  192. &resource.Filename,
  193. &resource.ExternalLink,
  194. &resource.Type,
  195. &resource.Size,
  196. &resource.CreatorID,
  197. &resource.CreatedTs,
  198. &resource.UpdatedTs,
  199. &resource.InternalPath,
  200. }
  201. if err := s.db.QueryRowContext(ctx, stmt, args...).Scan(dests...); err != nil {
  202. return nil, err
  203. }
  204. return &resource, nil
  205. }
  206. func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) error {
  207. stmt := `
  208. DELETE FROM resource
  209. WHERE id = ?
  210. `
  211. result, err := s.db.ExecContext(ctx, stmt, delete.ID)
  212. if err != nil {
  213. return err
  214. }
  215. if _, err := result.RowsAffected(); err != nil {
  216. return err
  217. }
  218. if err := s.Vacuum(ctx); err != nil {
  219. // Prevent linter warning.
  220. return err
  221. }
  222. return nil
  223. }
  224. func vacuumResource(ctx context.Context, tx *sql.Tx) error {
  225. stmt := `
  226. DELETE FROM
  227. resource
  228. WHERE
  229. creator_id NOT IN (
  230. SELECT
  231. id
  232. FROM
  233. user
  234. )`
  235. _, err := tx.ExecContext(ctx, stmt)
  236. if err != nil {
  237. return err
  238. }
  239. return nil
  240. }