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.
		
		
		
		
		
			
		
			
				
					
					
						
							418 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							418 lines
						
					
					
						
							12 KiB
						
					
					
				
								package services
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"errors"
							 | 
						|
									"git.aiterp.net/rpdata/api/internal/generate"
							 | 
						|
									"git.aiterp.net/rpdata/api/models"
							 | 
						|
									"git.aiterp.net/rpdata/api/models/changekeys"
							 | 
						|
									"git.aiterp.net/rpdata/api/repositories"
							 | 
						|
									"time"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// StoryService is a service governing all operations on stories and child objects.
							 | 
						|
								type StoryService struct {
							 | 
						|
									stories          repositories.StoryRepository
							 | 
						|
									chapters         repositories.ChapterRepository
							 | 
						|
									comments         repositories.CommentRepository
							 | 
						|
									changeService    *ChangeService
							 | 
						|
									characterService *CharacterService
							 | 
						|
									authService      *AuthService
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) FindStory(ctx context.Context, id string) (*models.Story, error) {
							 | 
						|
									return s.stories.Find(ctx, id)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) FindChapter(ctx context.Context, id string) (*models.Chapter, error) {
							 | 
						|
									return s.chapters.Find(ctx, id)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) FindComment(ctx context.Context, id string) (*models.Comment, error) {
							 | 
						|
									return s.comments.Find(ctx, id)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) ListStories(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
							 | 
						|
									return s.stories.List(ctx, filter)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) ListChapters(ctx context.Context, story models.Story) ([]*models.Chapter, error) {
							 | 
						|
									return s.chapters.List(ctx, models.ChapterFilter{StoryID: &story.ID, Limit: 0})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) ListComments(ctx context.Context, chapter models.Chapter, limit int) ([]*models.Comment, error) {
							 | 
						|
									return s.comments.List(ctx, models.CommentFilter{ChapterID: &chapter.ID, Limit: limit})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) CreateStory(ctx context.Context, name string, author *string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (*models.Story, error) {
							 | 
						|
									if author == nil {
							 | 
						|
										token := s.authService.TokenFromContext(ctx)
							 | 
						|
										if token == nil {
							 | 
						|
											return nil, ErrUnauthenticated
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										author = &token.UserID
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									story := &models.Story{
							 | 
						|
										Name:          name,
							 | 
						|
										Author:        *author,
							 | 
						|
										Category:      category,
							 | 
						|
										Listed:        listed,
							 | 
						|
										Open:          open,
							 | 
						|
										Tags:          tags,
							 | 
						|
										CreatedDate:   createdDate,
							 | 
						|
										FictionalDate: fictionalDate,
							 | 
						|
										UpdatedDate:   createdDate,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "add", story); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									story, err := s.stories.Insert(ctx, *story)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.changeService.Submit(ctx, "Story", "add", story.Listed, changekeys.Listed(story), story)
							 | 
						|
								
							 | 
						|
									return story, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) CreateChapter(ctx context.Context, story models.Story, title, source string, author *string, createdDate time.Time, fictionalDate *time.Time, commentMode models.ChapterCommentMode) (*models.Chapter, error) {
							 | 
						|
									if author == nil {
							 | 
						|
										token := s.authService.TokenFromContext(ctx)
							 | 
						|
										if token == nil {
							 | 
						|
											return nil, ErrUnauthenticated
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										author = &token.UserID
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									chapter := &models.Chapter{
							 | 
						|
										ID:             generate.ChapterID(),
							 | 
						|
										StoryID:        story.ID,
							 | 
						|
										Title:          title,
							 | 
						|
										Author:         *author,
							 | 
						|
										Source:         source,
							 | 
						|
										CreatedDate:    createdDate,
							 | 
						|
										EditedDate:     createdDate,
							 | 
						|
										CommentMode:    commentMode,
							 | 
						|
										CommentsLocked: false,
							 | 
						|
									}
							 | 
						|
									if fictionalDate != nil {
							 | 
						|
										chapter.FictionalDate = *fictionalDate
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if story.Open {
							 | 
						|
										if !s.authService.TokenFromContext(ctx).Permitted("member", "chapter.add") {
							 | 
						|
											return nil, ErrUnauthorized
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										if err := s.authService.CheckPermission(ctx, "add", chapter); err != nil {
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									chapter, err := s.chapters.Insert(ctx, *chapter)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if createdDate.After(story.UpdatedDate) {
							 | 
						|
										_, _ = s.stories.Update(ctx, story, models.StoryUpdate{UpdatedDate: &createdDate})
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.changeService.Submit(ctx, "Chapter", "add", story.Listed, changekeys.Many(story, chapter), chapter)
							 | 
						|
								
							 | 
						|
									return chapter, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter, subject, author, source, characterName string, characterID *string, createdDate time.Time, fictionalDate time.Time) (*models.Comment, error) {
							 | 
						|
									if characterID != nil {
							 | 
						|
										if err := s.permittedCharacter(ctx, "comment", *characterID); err != nil {
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										characterID = new(string)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if !chapter.CanComment() {
							 | 
						|
										return nil, errors.New("comments are locked or disabled")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if author == "" {
							 | 
						|
										if token := s.authService.TokenFromContext(ctx); token != nil {
							 | 
						|
											author = token.UserID
							 | 
						|
										} else {
							 | 
						|
											return nil, ErrUnauthenticated
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									comment := &models.Comment{
							 | 
						|
										ID:            generate.CommentID(),
							 | 
						|
										ChapterID:     chapter.ID,
							 | 
						|
										Subject:       subject,
							 | 
						|
										Author:        author,
							 | 
						|
										CharacterName: characterName,
							 | 
						|
										CharacterID:   *characterID,
							 | 
						|
										FictionalDate: fictionalDate,
							 | 
						|
										CreatedDate:   createdDate,
							 | 
						|
										EditedDate:    createdDate,
							 | 
						|
										Source:        source,
							 | 
						|
									}
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "add", comment); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									comment, err := s.comments.Insert(ctx, *comment)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
							 | 
						|
										s.changeService.Submit(ctx, "Comment", "add", story.Listed, changekeys.Many(story, chapter, comment), comment)
							 | 
						|
									} else {
							 | 
						|
										s.changeService.Submit(ctx, "Comment", "add", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter, comment), comment)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return comment, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) EditStory(ctx context.Context, story *models.Story, name *string, category *models.StoryCategory, listed, open *bool, fictionalDate *time.Time) (*models.Story, error) {
							 | 
						|
									if story == nil {
							 | 
						|
										panic("StoryService.Edit called with nil story")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "edit", story); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									story, err := s.stories.Update(ctx, *story, models.StoryUpdate{
							 | 
						|
										Name:          name,
							 | 
						|
										Open:          open,
							 | 
						|
										Listed:        listed,
							 | 
						|
										Category:      category,
							 | 
						|
										FictionalDate: fictionalDate,
							 | 
						|
									})
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.changeService.Submit(ctx, "Story", "edit", story.Listed, changekeys.Listed(story), story)
							 | 
						|
								
							 | 
						|
									return story, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) AddStoryTag(ctx context.Context, story models.Story, tag models.Tag) (*models.Story, error) {
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "edit", &story); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									err := s.stories.AddTag(ctx, story, tag)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									story.Tags = append(story.Tags, tag)
							 | 
						|
								
							 | 
						|
									s.changeService.Submit(ctx, "Story", "tag", story.Listed, changekeys.Listed(story), story, tag)
							 | 
						|
								
							 | 
						|
									return &story, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) RemoveStoryTag(ctx context.Context, story models.Story, tag models.Tag) (*models.Story, error) {
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "edit", &story); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									err := s.stories.RemoveTag(ctx, story, tag)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for i, tag2 := range story.Tags {
							 | 
						|
										if tag2 == tag {
							 | 
						|
											story.Tags = append(story.Tags[:i], story.Tags[i+1:]...)
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.changeService.Submit(ctx, "Story", "untag", story.Listed, changekeys.Listed(story), story, tag)
							 | 
						|
								
							 | 
						|
									return &story, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) EditChapter(ctx context.Context, chapter *models.Chapter, title, source *string, fictionalDate *time.Time, commentMode *models.ChapterCommentMode, commentsLocked *bool) (*models.Chapter, error) {
							 | 
						|
									if chapter == nil {
							 | 
						|
										panic("StoryService.EditChapter called with nil chapter")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "edit", chapter); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									chapter, err := s.chapters.Update(ctx, *chapter, models.ChapterUpdate{
							 | 
						|
										Title:          title,
							 | 
						|
										Source:         source,
							 | 
						|
										FictionalDate:  fictionalDate,
							 | 
						|
										CommentMode:    commentMode,
							 | 
						|
										CommentsLocked: commentsLocked,
							 | 
						|
									})
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
							 | 
						|
										s.changeService.Submit(ctx, "Chapter", "edit", story.Listed, changekeys.Many(story, chapter), chapter)
							 | 
						|
									} else {
							 | 
						|
										s.changeService.Submit(ctx, "Chapter", "edit", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter), chapter)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return chapter, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) MoveChapter(ctx context.Context, chapter *models.Chapter, from, to models.Story) (*models.Chapter, error) {
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "move", chapter); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if to.Open {
							 | 
						|
										if !s.authService.TokenFromContext(ctx).Permitted("member", "chapter.add") {
							 | 
						|
											return nil, ErrUnauthorized
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										if err := s.authService.CheckPermission(ctx, "add", chapter); err != nil {
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									chapter, err := s.chapters.Move(ctx, *chapter, from, to)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.changeService.Submit(ctx, "Chapter", "move-out", from.Listed, changekeys.Listed(from), chapter)
							 | 
						|
									s.changeService.Submit(ctx, "Chapter", "move-in", to.Listed, changekeys.Listed(to), chapter)
							 | 
						|
								
							 | 
						|
									return chapter, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) EditComment(ctx context.Context, comment *models.Comment, source, characterName, characterID, subject *string, fictionalDate *time.Time) (*models.Comment, error) {
							 | 
						|
									if comment == nil {
							 | 
						|
										panic("StoryService.EditChapter called with nil chapter")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "edit", comment); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if characterID != nil && *characterID != "" && *characterID != comment.CharacterID {
							 | 
						|
										if err := s.permittedCharacter(ctx, "comment", *characterID); err != nil {
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									chapter, err := s.chapters.Find(ctx, comment.ChapterID)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, errors.New("could not find chapter")
							 | 
						|
									}
							 | 
						|
									if !chapter.CanComment() {
							 | 
						|
										return nil, errors.New("comments are locked or disabled")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									comment, err = s.comments.Update(ctx, *comment, models.CommentUpdate{
							 | 
						|
										Source:        source,
							 | 
						|
										CharacterName: characterName,
							 | 
						|
										CharacterID:   characterID,
							 | 
						|
										FictionalDate: fictionalDate,
							 | 
						|
										Subject:       subject,
							 | 
						|
									})
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
							 | 
						|
										s.changeService.Submit(ctx, "Comment", "edit", story.Listed, changekeys.Many(story, chapter, comment), comment)
							 | 
						|
									} else {
							 | 
						|
										s.changeService.Submit(ctx, "Comment", "edit", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter, comment), comment)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return comment, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) RemoveStory(ctx context.Context, story *models.Story) error {
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "add", story); err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									err := s.stories.Delete(ctx, *story)
							 | 
						|
									if err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.changeService.Submit(ctx, "Story", "remove", story.Listed, changekeys.Listed(story), story)
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) RemoveChapter(ctx context.Context, chapter *models.Chapter) error {
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "remove", chapter); err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									err := s.chapters.Delete(ctx, *chapter)
							 | 
						|
									if err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
							 | 
						|
										s.changeService.Submit(ctx, "Chapter", "remove", story.Listed, changekeys.Many(story, chapter), chapter)
							 | 
						|
									} else {
							 | 
						|
										s.changeService.Submit(ctx, "Chapter", "remove", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter), chapter)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) RemoveComment(ctx context.Context, comment *models.Comment) error {
							 | 
						|
									if err := s.authService.CheckPermission(ctx, "remove", comment); err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									chapter, err := s.chapters.Find(ctx, comment.ChapterID)
							 | 
						|
									if err != nil {
							 | 
						|
										return errors.New("could not find parent chapter")
							 | 
						|
									}
							 | 
						|
									if !chapter.CanComment() {
							 | 
						|
										return errors.New("comments are locked or disabled")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									err = s.comments.Delete(ctx, *comment)
							 | 
						|
									if err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
							 | 
						|
										s.changeService.Submit(ctx, "Comment", "remove", story.Listed, changekeys.Many(story, chapter, comment), comment)
							 | 
						|
									} else {
							 | 
						|
										s.changeService.Submit(ctx, "Comment", "remove", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter, comment), comment)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *StoryService) permittedCharacter(ctx context.Context, permissionKind, characterID string) error {
							 | 
						|
									character, err := s.characterService.Find(ctx, characterID)
							 | 
						|
									if err != nil {
							 | 
						|
										return errors.New("character could not be found")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									token := s.authService.TokenFromContext(ctx)
							 | 
						|
									if character.Author != token.UserID && !token.Permitted(permissionKind+".edit") {
							 | 
						|
										return errors.New("you are not permitted to use others' character")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 |