This commit is contained in:
@@ -21,6 +21,7 @@ type MongoDB struct {
|
|||||||
logs *logRepository
|
logs *logRepository
|
||||||
posts *postRepository
|
posts *postRepository
|
||||||
files *fileRepository
|
files *fileRepository
|
||||||
|
story repositories.StoryRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MongoDB) Changes() repositories.ChangeRepository {
|
func (m *MongoDB) Changes() repositories.ChangeRepository {
|
||||||
@@ -51,6 +52,10 @@ func (m *MongoDB) Files() repositories.FileRepository {
|
|||||||
return m.files
|
return m.files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MongoDB) Story() repositories.StoryRepository {
|
||||||
|
return m.story
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MongoDB) Close(ctx context.Context) error {
|
func (m *MongoDB) Close(ctx context.Context) error {
|
||||||
m.session.Close()
|
m.session.Close()
|
||||||
return nil
|
return nil
|
||||||
@@ -114,6 +119,12 @@ func Init(cfg config.Database) (*MongoDB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
story, err := newStoryRepository(db)
|
||||||
|
if err != nil {
|
||||||
|
session.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
go posts.fixPositions(logs)
|
go posts.fixPositions(logs)
|
||||||
|
|
||||||
return &MongoDB{
|
return &MongoDB{
|
||||||
@@ -123,6 +134,7 @@ func Init(cfg config.Database) (*MongoDB, error) {
|
|||||||
characters: characters,
|
characters: characters,
|
||||||
channels: channels,
|
channels: channels,
|
||||||
tags: newTagRepository(db),
|
tags: newTagRepository(db),
|
||||||
|
story: story,
|
||||||
logs: logs,
|
logs: logs,
|
||||||
posts: posts,
|
posts: posts,
|
||||||
files: files,
|
files: files,
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.aiterp.net/rpdata/api/internal/generate"
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
"git.aiterp.net/rpdata/api/repositories"
|
||||||
|
"github.com/globalsign/mgo"
|
||||||
|
"github.com/globalsign/mgo/bson"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type storyRepository struct {
|
||||||
|
stories *mgo.Collection
|
||||||
|
chapters *mgo.Collection
|
||||||
|
comments *mgo.Collection
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStoryRepository(db *mgo.Database) (repositories.StoryRepository, error) {
|
||||||
|
collection := db.C("story.stories")
|
||||||
|
|
||||||
|
err := collection.EnsureIndexKey("tags")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = collection.EnsureIndexKey("author")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = collection.EnsureIndexKey("updatedDate")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = collection.EnsureIndexKey("fictionalDate")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = collection.EnsureIndexKey("listed")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &storyRepository{
|
||||||
|
stories: collection,
|
||||||
|
chapters: db.C("story.chapters"),
|
||||||
|
comments: db.C("story.comments"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storyRepository) Find(ctx context.Context, id string) (*models.Story, error) {
|
||||||
|
story := new(models.Story)
|
||||||
|
err := r.stories.FindId(id).One(story)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return story, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
|
||||||
|
query := bson.M{}
|
||||||
|
if filter.Author != nil {
|
||||||
|
query["author"] = *filter.Author
|
||||||
|
}
|
||||||
|
if filter.Category != nil {
|
||||||
|
query["category"] = *filter.Category
|
||||||
|
}
|
||||||
|
if filter.Open != nil {
|
||||||
|
query["open"] = *filter.Open
|
||||||
|
}
|
||||||
|
if len(filter.Tags) > 0 {
|
||||||
|
query["tags"] = bson.M{"$all": filter.Tags}
|
||||||
|
}
|
||||||
|
if filter.Unlisted != nil {
|
||||||
|
query["listed"] = *filter.Unlisted
|
||||||
|
}
|
||||||
|
if !filter.EarliestFictionalDate.IsZero() && !filter.LatestFictionalDate.IsZero() {
|
||||||
|
query["fictionalDate"] = bson.M{
|
||||||
|
"$gte": filter.EarliestFictionalDate,
|
||||||
|
"$lte": filter.LatestFictionalDate,
|
||||||
|
}
|
||||||
|
} else if !filter.EarliestFictionalDate.IsZero() {
|
||||||
|
query["fictionalDate"] = bson.M{
|
||||||
|
"$gte": filter.EarliestFictionalDate,
|
||||||
|
}
|
||||||
|
} else if !filter.LatestFictionalDate.IsZero() {
|
||||||
|
query["fictionalDate"] = bson.M{
|
||||||
|
"$lte": filter.LatestFictionalDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stories := make([]*models.Story, 0, 32)
|
||||||
|
err := r.stories.Find(query).Sort("-updatedDate ").Limit(filter.Limit).All(&stories)
|
||||||
|
if err != nil {
|
||||||
|
if err == mgo.ErrNotFound {
|
||||||
|
return stories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storyRepository) Insert(ctx context.Context, story models.Story) (*models.Story, error) {
|
||||||
|
story.ID = generate.StoryID()
|
||||||
|
|
||||||
|
err := r.stories.Insert(story)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &story, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storyRepository) Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error) {
|
||||||
|
updateBson := bson.M{}
|
||||||
|
if update.Name != nil {
|
||||||
|
updateBson["name"] = *update.Name
|
||||||
|
story.Name = *update.Name
|
||||||
|
}
|
||||||
|
if update.Open != nil {
|
||||||
|
updateBson["open"] = *update.Open
|
||||||
|
story.Open = *update.Open
|
||||||
|
}
|
||||||
|
if update.Category != nil {
|
||||||
|
updateBson["category"] = *update.Category
|
||||||
|
story.Category = *update.Category
|
||||||
|
}
|
||||||
|
if update.Author != nil {
|
||||||
|
updateBson["author"] = *update.Author
|
||||||
|
story.Author = *update.Author
|
||||||
|
}
|
||||||
|
if update.FictionalDate != nil {
|
||||||
|
updateBson["fictionalDate"] = *update.FictionalDate
|
||||||
|
story.FictionalDate = *update.FictionalDate
|
||||||
|
}
|
||||||
|
if update.UpdatedDate != nil {
|
||||||
|
updateBson["updatedDate"] = *update.UpdatedDate
|
||||||
|
story.UpdatedDate = *update.UpdatedDate
|
||||||
|
}
|
||||||
|
if update.Listed != nil {
|
||||||
|
updateBson["listed"] = *update.Listed
|
||||||
|
story.Listed = *update.Listed
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.stories.UpdateId(story.ID, bson.M{"$set": updateBson})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &story, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storyRepository) AddTag(ctx context.Context, story models.Story, tag models.Tag) error {
|
||||||
|
ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$addToSet": bson.M{"tags": tag}})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ci.Updated == 0 {
|
||||||
|
return repositories.ErrTagExists
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storyRepository) RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error {
|
||||||
|
ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$pull": bson.M{"tags": tag}})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ci.Updated == 0 {
|
||||||
|
return repositories.ErrTagDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *storyRepository) Delete(ctx context.Context, story models.Story) error {
|
||||||
|
err := r.stories.RemoveId(story.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chapterIds := make([]string, 0, 8)
|
||||||
|
err = r.chapters.Find(bson.M{"storyId": story.ID}).Distinct("_id", &chapterIds)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to find chapterIds:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(chapterIds) > 0 {
|
||||||
|
c1, err := r.chapters.RemoveAll(bson.M{"storyId": story.ID})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to remove chapters:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c2, err := r.chapters.RemoveAll(bson.M{"comments": bson.M{"$in": chapterIds}})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to remove comments:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Removed story %s (%d chapters, %d comments)", story.ID, c1.Removed, c2.Removed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -53,6 +53,11 @@ func ChapterID() string {
|
|||||||
return ID("SC", 24)
|
return ID("SC", 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommentID generates a post ID.
|
||||||
|
func CommentID() string {
|
||||||
|
return ID("SCC", 32)
|
||||||
|
}
|
||||||
|
|
||||||
// FileID generates a file ID.
|
// FileID generates a file ID.
|
||||||
func FileID() string {
|
func FileID() string {
|
||||||
return ID("F", 16)
|
return ID("F", 16)
|
||||||
|
|||||||
+14
-1
@@ -25,4 +25,17 @@ func (chapter *Chapter) CanComment() bool {
|
|||||||
// ChangeObject in GQL.
|
// ChangeObject in GQL.
|
||||||
func (*Chapter) IsChangeObject() {
|
func (*Chapter) IsChangeObject() {
|
||||||
panic("this method is a dummy, and so is its caller")
|
panic("this method is a dummy, and so is its caller")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChapterFilter struct {
|
||||||
|
StoryID *string
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapterUpdate struct {
|
||||||
|
Title *string
|
||||||
|
Source *string
|
||||||
|
FictionalDate *time.Time
|
||||||
|
CommentMode *ChapterCommentMode
|
||||||
|
CommentsLocked *bool
|
||||||
|
}
|
||||||
|
|||||||
+15
-2
@@ -12,7 +12,7 @@ type Comment struct {
|
|||||||
CharacterID string `bson:"characterId"`
|
CharacterID string `bson:"characterId"`
|
||||||
FictionalDate time.Time `bson:"fictionalDate"`
|
FictionalDate time.Time `bson:"fictionalDate"`
|
||||||
CreatedDate time.Time `bson:"createdDate"`
|
CreatedDate time.Time `bson:"createdDate"`
|
||||||
EditedDate time.Time `bson:"editeddDate"`
|
EditedDate time.Time `bson:"editedDate"`
|
||||||
Source string `bson:"source"`
|
Source string `bson:"source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,4 +20,17 @@ type Comment struct {
|
|||||||
// ChangeObject in GQL.
|
// ChangeObject in GQL.
|
||||||
func (*Comment) IsChangeObject() {
|
func (*Comment) IsChangeObject() {
|
||||||
panic("this method is a dummy, and so is its caller")
|
panic("this method is a dummy, and so is its caller")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentFilter struct {
|
||||||
|
ChapterID *string
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentUpdate struct {
|
||||||
|
Source *string
|
||||||
|
CharacterName *string
|
||||||
|
CharacterID *string
|
||||||
|
Subject *string
|
||||||
|
FictionalDate *time.Time
|
||||||
|
}
|
||||||
|
|||||||
+22
-1
@@ -21,4 +21,25 @@ type Story struct {
|
|||||||
// ChangeObject in GQL.
|
// ChangeObject in GQL.
|
||||||
func (*Story) IsChangeObject() {
|
func (*Story) IsChangeObject() {
|
||||||
panic("this method is a dummy, and so is its caller")
|
panic("this method is a dummy, and so is its caller")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StoryFilter struct {
|
||||||
|
Author *string
|
||||||
|
Tags []Tag
|
||||||
|
EarliestFictionalDate time.Time
|
||||||
|
LatestFictionalDate time.Time
|
||||||
|
Category *StoryCategory
|
||||||
|
Open *bool
|
||||||
|
Unlisted *bool
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
type StoryUpdate struct {
|
||||||
|
Name *string
|
||||||
|
Category *StoryCategory
|
||||||
|
Author *string
|
||||||
|
Open *bool
|
||||||
|
Listed *bool
|
||||||
|
FictionalDate *time.Time
|
||||||
|
UpdatedDate *time.Time
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChapterRepository interface {
|
||||||
|
Find(ctx context.Context, id string) (*models.Chapter, error)
|
||||||
|
List(ctx context.Context, filter models.ChapterFilter) ([]*models.Chapter, error)
|
||||||
|
Insert(ctx context.Context, chapter models.Chapter) (*models.Chapter, error)
|
||||||
|
Update(ctx context.Context, chapter models.Chapter, update models.ChapterUpdate) (*models.Story, error)
|
||||||
|
Delete(ctx context.Context, chapter models.Chapter) error
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommentRepository interface {
|
||||||
|
Find(ctx context.Context, id string) (*models.Comment, error)
|
||||||
|
List(ctx context.Context, filter models.CommentFilter) ([]*models.Comment, error)
|
||||||
|
Insert(ctx context.Context, comment models.Comment) (*models.Comment, error)
|
||||||
|
Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Story, error)
|
||||||
|
Delete(ctx context.Context, comment models.Comment) error
|
||||||
|
}
|
||||||
@@ -10,3 +10,9 @@ var ErrInvalidPosition = errors.New("position is out of bounds")
|
|||||||
|
|
||||||
// ErrParentMismatch is returned if the list refer to different parent objects when it shouldn't.
|
// ErrParentMismatch is returned if the list refer to different parent objects when it shouldn't.
|
||||||
var ErrParentMismatch = errors.New("parent resource is not the same for the entire list")
|
var ErrParentMismatch = errors.New("parent resource is not the same for the entire list")
|
||||||
|
|
||||||
|
// ErrTagExists is returned if an existing tag is added again.
|
||||||
|
var ErrTagExists = errors.New("tag already exists")
|
||||||
|
|
||||||
|
// ErrTagDoesNotExist is returned if a non-existing tag is attempted removed.
|
||||||
|
var ErrTagDoesNotExist = errors.New("tag already exists")
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.aiterp.net/rpdata/api/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StoryRepository interface {
|
||||||
|
Find(ctx context.Context, id string) (*models.Story, error)
|
||||||
|
List(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error)
|
||||||
|
Insert(ctx context.Context, story models.Story) (*models.Story, error)
|
||||||
|
Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error)
|
||||||
|
AddTag(ctx context.Context, story models.Story, tag models.Tag) error
|
||||||
|
RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error
|
||||||
|
Delete(ctx context.Context, story models.Story) error
|
||||||
|
}
|
||||||
@@ -45,6 +45,21 @@ func (s *ChangeService) Submit(ctx context.Context, model models.ChangeModel, op
|
|||||||
Date: time.Now(),
|
Date: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not allow * keys in unlisted objects, but allow laziness on the
|
||||||
|
// call site to not bloat the code too much.
|
||||||
|
if !listed {
|
||||||
|
deleteList := make([]int, 0, 2)
|
||||||
|
for i, key := range keys {
|
||||||
|
if key.ID == "*" {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, index := range deleteList {
|
||||||
|
change.Keys = append(change.Keys[:index], change.Keys[index+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, obj := range objects {
|
for _, obj := range objects {
|
||||||
if !change.AddObject(obj) {
|
if !change.AddObject(obj) {
|
||||||
log.Printf("Cannot add object of type %T to change", obj)
|
log.Printf("Cannot add object of type %T to change", obj)
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"git.aiterp.net/rpdata/api/internal/auth"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StoryService) FindStory(ctx context.Context, id string) (*models.Story, error) {
|
||||||
|
return s.stories.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, author string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (*models.Story, error) {
|
||||||
|
story := &models.Story{
|
||||||
|
Name: name,
|
||||||
|
Author: author,
|
||||||
|
Category: category,
|
||||||
|
Listed: listed,
|
||||||
|
Open: open,
|
||||||
|
Tags: tags,
|
||||||
|
CreatedDate: createdDate,
|
||||||
|
FictionalDate: fictionalDate,
|
||||||
|
UpdatedDate: createdDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := auth.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, author, source string, createdDate time.Time, fictionalDate *time.Time, commentMode models.ChapterCommentMode) (*models.Chapter, error) {
|
||||||
|
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 !auth.TokenFromContext(ctx).Permitted("member", "chapter.add") {
|
||||||
|
return nil, auth.ErrUnauthorized
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := auth.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateComment adds a comment.
|
||||||
|
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) {
|
||||||
|
characterID := ""
|
||||||
|
if character != nil {
|
||||||
|
characterID = character.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if !chapter.CanComment() {
|
||||||
|
return nil, errors.New("comments are locked or disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if author == "" {
|
||||||
|
if token := auth.TokenFromContext(ctx); token != nil {
|
||||||
|
author = token.UserID
|
||||||
|
} else {
|
||||||
|
return nil, auth.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 := auth.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(chapter, comment), comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return comment, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user