announcement_repo.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package repository
  2. import (
  3. "context"
  4. "strings"
  5. "time"
  6. dbent "github.com/Wei-Shaw/sub2api/ent"
  7. "github.com/Wei-Shaw/sub2api/ent/announcement"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. entsql "entgo.io/ent/dialect/sql"
  11. )
  12. type announcementRepository struct {
  13. client *dbent.Client
  14. }
  15. func NewAnnouncementRepository(client *dbent.Client) service.AnnouncementRepository {
  16. return &announcementRepository{client: client}
  17. }
  18. func (r *announcementRepository) Create(ctx context.Context, a *service.Announcement) error {
  19. client := clientFromContext(ctx, r.client)
  20. builder := client.Announcement.Create().
  21. SetTitle(a.Title).
  22. SetContent(a.Content).
  23. SetStatus(a.Status).
  24. SetNotifyMode(a.NotifyMode).
  25. SetTargeting(a.Targeting)
  26. if a.StartsAt != nil {
  27. builder.SetStartsAt(*a.StartsAt)
  28. }
  29. if a.EndsAt != nil {
  30. builder.SetEndsAt(*a.EndsAt)
  31. }
  32. if a.CreatedBy != nil {
  33. builder.SetCreatedBy(*a.CreatedBy)
  34. }
  35. if a.UpdatedBy != nil {
  36. builder.SetUpdatedBy(*a.UpdatedBy)
  37. }
  38. created, err := builder.Save(ctx)
  39. if err != nil {
  40. return err
  41. }
  42. applyAnnouncementEntityToService(a, created)
  43. return nil
  44. }
  45. func (r *announcementRepository) GetByID(ctx context.Context, id int64) (*service.Announcement, error) {
  46. m, err := r.client.Announcement.Query().
  47. Where(announcement.IDEQ(id)).
  48. Only(ctx)
  49. if err != nil {
  50. return nil, translatePersistenceError(err, service.ErrAnnouncementNotFound, nil)
  51. }
  52. return announcementEntityToService(m), nil
  53. }
  54. func (r *announcementRepository) Update(ctx context.Context, a *service.Announcement) error {
  55. client := clientFromContext(ctx, r.client)
  56. builder := client.Announcement.UpdateOneID(a.ID).
  57. SetTitle(a.Title).
  58. SetContent(a.Content).
  59. SetStatus(a.Status).
  60. SetNotifyMode(a.NotifyMode).
  61. SetTargeting(a.Targeting)
  62. if a.StartsAt != nil {
  63. builder.SetStartsAt(*a.StartsAt)
  64. } else {
  65. builder.ClearStartsAt()
  66. }
  67. if a.EndsAt != nil {
  68. builder.SetEndsAt(*a.EndsAt)
  69. } else {
  70. builder.ClearEndsAt()
  71. }
  72. if a.CreatedBy != nil {
  73. builder.SetCreatedBy(*a.CreatedBy)
  74. } else {
  75. builder.ClearCreatedBy()
  76. }
  77. if a.UpdatedBy != nil {
  78. builder.SetUpdatedBy(*a.UpdatedBy)
  79. } else {
  80. builder.ClearUpdatedBy()
  81. }
  82. updated, err := builder.Save(ctx)
  83. if err != nil {
  84. return translatePersistenceError(err, service.ErrAnnouncementNotFound, nil)
  85. }
  86. a.UpdatedAt = updated.UpdatedAt
  87. return nil
  88. }
  89. func (r *announcementRepository) Delete(ctx context.Context, id int64) error {
  90. client := clientFromContext(ctx, r.client)
  91. _, err := client.Announcement.Delete().Where(announcement.IDEQ(id)).Exec(ctx)
  92. return err
  93. }
  94. func (r *announcementRepository) List(
  95. ctx context.Context,
  96. params pagination.PaginationParams,
  97. filters service.AnnouncementListFilters,
  98. ) ([]service.Announcement, *pagination.PaginationResult, error) {
  99. q := r.client.Announcement.Query()
  100. if filters.Status != "" {
  101. q = q.Where(announcement.StatusEQ(filters.Status))
  102. }
  103. if filters.Search != "" {
  104. q = q.Where(
  105. announcement.Or(
  106. announcement.TitleContainsFold(filters.Search),
  107. announcement.ContentContainsFold(filters.Search),
  108. ),
  109. )
  110. }
  111. total, err := q.Count(ctx)
  112. if err != nil {
  113. return nil, nil, err
  114. }
  115. itemsQuery := q.
  116. Offset(params.Offset()).
  117. Limit(params.Limit())
  118. for _, order := range announcementListOrders(params) {
  119. itemsQuery = itemsQuery.Order(order)
  120. }
  121. items, err := itemsQuery.All(ctx)
  122. if err != nil {
  123. return nil, nil, err
  124. }
  125. out := announcementEntitiesToService(items)
  126. return out, paginationResultFromTotal(int64(total), params), nil
  127. }
  128. func announcementListOrder(params pagination.PaginationParams) (string, string) {
  129. sortBy := strings.ToLower(strings.TrimSpace(params.SortBy))
  130. sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc)
  131. switch sortBy {
  132. case "title":
  133. return announcement.FieldTitle, sortOrder
  134. case "status":
  135. return announcement.FieldStatus, sortOrder
  136. case "notify_mode":
  137. return announcement.FieldNotifyMode, sortOrder
  138. case "starts_at":
  139. return announcement.FieldStartsAt, sortOrder
  140. case "ends_at":
  141. return announcement.FieldEndsAt, sortOrder
  142. case "id":
  143. return announcement.FieldID, sortOrder
  144. case "", "created_at":
  145. return announcement.FieldCreatedAt, sortOrder
  146. default:
  147. return announcement.FieldCreatedAt, pagination.SortOrderDesc
  148. }
  149. }
  150. func announcementListOrders(params pagination.PaginationParams) []func(*entsql.Selector) {
  151. field, sortOrder := announcementListOrder(params)
  152. if sortOrder == pagination.SortOrderAsc {
  153. if field == announcement.FieldID {
  154. return []func(*entsql.Selector){
  155. dbent.Asc(field),
  156. }
  157. }
  158. return []func(*entsql.Selector){
  159. dbent.Asc(field),
  160. dbent.Asc(announcement.FieldID),
  161. }
  162. }
  163. if field == announcement.FieldID {
  164. return []func(*entsql.Selector){
  165. dbent.Desc(field),
  166. }
  167. }
  168. return []func(*entsql.Selector){
  169. dbent.Desc(field),
  170. dbent.Desc(announcement.FieldID),
  171. }
  172. }
  173. func (r *announcementRepository) ListActive(ctx context.Context, now time.Time) ([]service.Announcement, error) {
  174. q := r.client.Announcement.Query().
  175. Where(
  176. announcement.StatusEQ(service.AnnouncementStatusActive),
  177. announcement.Or(announcement.StartsAtIsNil(), announcement.StartsAtLTE(now)),
  178. announcement.Or(announcement.EndsAtIsNil(), announcement.EndsAtGT(now)),
  179. ).
  180. Order(dbent.Desc(announcement.FieldID)).
  181. Limit(200)
  182. items, err := q.All(ctx)
  183. if err != nil {
  184. return nil, err
  185. }
  186. return announcementEntitiesToService(items), nil
  187. }
  188. func applyAnnouncementEntityToService(dst *service.Announcement, src *dbent.Announcement) {
  189. if dst == nil || src == nil {
  190. return
  191. }
  192. dst.ID = src.ID
  193. dst.CreatedAt = src.CreatedAt
  194. dst.UpdatedAt = src.UpdatedAt
  195. }
  196. func announcementEntityToService(m *dbent.Announcement) *service.Announcement {
  197. if m == nil {
  198. return nil
  199. }
  200. return &service.Announcement{
  201. ID: m.ID,
  202. Title: m.Title,
  203. Content: m.Content,
  204. Status: m.Status,
  205. NotifyMode: m.NotifyMode,
  206. Targeting: m.Targeting,
  207. StartsAt: m.StartsAt,
  208. EndsAt: m.EndsAt,
  209. CreatedBy: m.CreatedBy,
  210. UpdatedBy: m.UpdatedBy,
  211. CreatedAt: m.CreatedAt,
  212. UpdatedAt: m.UpdatedAt,
  213. }
  214. }
  215. func announcementEntitiesToService(models []*dbent.Announcement) []service.Announcement {
  216. out := make([]service.Announcement, 0, len(models))
  217. for i := range models {
  218. if s := announcementEntityToService(models[i]); s != nil {
  219. out = append(out, *s)
  220. }
  221. }
  222. return out
  223. }