resource.go 5.4 KB

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