GraphQL API and utilities for the rpdata project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

152 lines
4.5 KiB

  1. package services
  2. import (
  3. "context"
  4. "errors"
  5. "git.aiterp.net/rpdata/api/internal/auth"
  6. "git.aiterp.net/rpdata/api/internal/generate"
  7. "git.aiterp.net/rpdata/api/models"
  8. "git.aiterp.net/rpdata/api/models/changekeys"
  9. "git.aiterp.net/rpdata/api/repositories"
  10. "time"
  11. )
  12. // StoryService is a service governing all operations on stories and child objects.
  13. type StoryService struct {
  14. stories repositories.StoryRepository
  15. chapters repositories.ChapterRepository
  16. comments repositories.CommentRepository
  17. changeService *ChangeService
  18. }
  19. func (s *StoryService) FindStory(ctx context.Context, id string) (*models.Story, error) {
  20. return s.stories.Find(ctx, id)
  21. }
  22. func (s *StoryService) ListStories(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
  23. return s.stories.List(ctx, filter)
  24. }
  25. func (s *StoryService) ListChapters(ctx context.Context, story models.Story) ([]*models.Chapter, error) {
  26. return s.chapters.List(ctx, models.ChapterFilter{StoryID: &story.ID, Limit: 0})
  27. }
  28. func (s *StoryService) ListComments(ctx context.Context, chapter models.Chapter, limit int) ([]*models.Comment, error) {
  29. return s.comments.List(ctx, models.CommentFilter{ChapterID: &chapter.ID, Limit: limit})
  30. }
  31. func (s *StoryService) CreateStory(ctx context.Context, name, author string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (*models.Story, error) {
  32. story := &models.Story{
  33. Name: name,
  34. Author: author,
  35. Category: category,
  36. Listed: listed,
  37. Open: open,
  38. Tags: tags,
  39. CreatedDate: createdDate,
  40. FictionalDate: fictionalDate,
  41. UpdatedDate: createdDate,
  42. }
  43. if err := auth.CheckPermission(ctx, "add", story); err != nil {
  44. return nil, err
  45. }
  46. story, err := s.stories.Insert(ctx, *story)
  47. if err != nil {
  48. return nil, err
  49. }
  50. s.changeService.Submit(ctx, "Story", "add", story.Listed, changekeys.Listed(story), story)
  51. return story, nil
  52. }
  53. func (s *StoryService) CreateChapter(ctx context.Context, story models.Story, title, author, source string, createdDate time.Time, fictionalDate *time.Time, commentMode models.ChapterCommentMode) (*models.Chapter, error) {
  54. chapter := &models.Chapter{
  55. ID: generate.ChapterID(),
  56. StoryID: story.ID,
  57. Title: title,
  58. Author: author,
  59. Source: source,
  60. CreatedDate: createdDate,
  61. EditedDate: createdDate,
  62. CommentMode: commentMode,
  63. CommentsLocked: false,
  64. }
  65. if fictionalDate != nil {
  66. chapter.FictionalDate = *fictionalDate
  67. }
  68. if story.Open {
  69. if !auth.TokenFromContext(ctx).Permitted("member", "chapter.add") {
  70. return nil, auth.ErrUnauthorized
  71. }
  72. } else {
  73. if err := auth.CheckPermission(ctx, "add", chapter); err != nil {
  74. return nil, err
  75. }
  76. }
  77. chapter, err := s.chapters.Insert(ctx, *chapter)
  78. if err != nil {
  79. return nil, err
  80. }
  81. if createdDate.After(story.UpdatedDate) {
  82. _, _ = s.stories.Update(ctx, story, models.StoryUpdate{UpdatedDate: &createdDate})
  83. }
  84. s.changeService.Submit(ctx, "Chapter", "add", story.Listed, changekeys.Many(story, chapter), chapter)
  85. return chapter, nil
  86. }
  87. // CreateComment adds a comment.
  88. func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter, subject, author, source, characterName string, character *models.Character, createdDate time.Time, fictionalDate time.Time) (*models.Comment, error) {
  89. characterID := ""
  90. if character != nil {
  91. characterID = character.ID
  92. }
  93. if !chapter.CanComment() {
  94. return nil, errors.New("comments are locked or disabled")
  95. }
  96. if author == "" {
  97. if token := auth.TokenFromContext(ctx); token != nil {
  98. author = token.UserID
  99. } else {
  100. return nil, auth.ErrUnauthenticated
  101. }
  102. }
  103. comment := &models.Comment{
  104. ID: generate.CommentID(),
  105. ChapterID: chapter.ID,
  106. Subject: subject,
  107. Author: author,
  108. CharacterName: characterName,
  109. CharacterID: characterID,
  110. FictionalDate: fictionalDate,
  111. CreatedDate: createdDate,
  112. EditedDate: createdDate,
  113. Source: source,
  114. }
  115. if err := auth.CheckPermission(ctx, "add", comment); err != nil {
  116. return nil, err
  117. }
  118. comment, err := s.comments.Insert(ctx, *comment)
  119. if err != nil {
  120. return nil, err
  121. }
  122. if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
  123. s.changeService.Submit(ctx, "Comment", "add", story.Listed, changekeys.Many(story, chapter, comment), comment)
  124. } else {
  125. s.changeService.Submit(ctx, "Comment", "add", false, changekeys.Many(chapter, comment), comment)
  126. }
  127. return comment, nil
  128. }