resource.go 5.5 KB

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