diff --git a/cmd/rpdata-restore/main.go b/cmd/rpdata-restore/main.go index 4e95b27..36b38e0 100644 --- a/cmd/rpdata-restore/main.go +++ b/cmd/rpdata-restore/main.go @@ -83,10 +83,6 @@ func main() { hideList := make(map[string]bool) - if parts[1] != "story" && parts[1] != "chapter" { - continue - } - log.Println("Loaded", parts[1], parts[2], "from archive.") switch parts[1] { @@ -159,6 +155,10 @@ func main() { log.Fatalln("Could not parse story:", parts[2], err) } + if *flagReplace { + _ = db.Stories().Delete(ctx, story) + } + _, err = db.Stories().Insert(ctx, story) if err != nil { log.Fatalln("Could not insert story:", parts[2], err) @@ -179,6 +179,10 @@ func main() { log.Fatalln("Could not parse story:", parts[2], err) } + if *flagReplace { + _ = db.Chapters().Delete(ctx, chapter) + } + _, err = db.Chapters().Insert(ctx, chapter) if err != nil { log.Fatalln("Could not insert story:", parts[2], err) @@ -199,6 +203,10 @@ func main() { log.Fatalln("Could not parse story:", parts[2], err) } + if *flagReplace { + _ = db.Comments().Delete(ctx, comment) + } + _, err = db.Comments().Insert(ctx, comment) if err != nil { log.Fatalln("Could not insert story:", parts[2], err) diff --git a/database/postgres/chapters.go b/database/postgres/chapters.go index c8db618..a6215ee 100644 --- a/database/postgres/chapters.go +++ b/database/postgres/chapters.go @@ -84,7 +84,7 @@ func (r *chapterRepository) Update(ctx context.Context, chapter models.Chapter, return &chapter, nil } -func (r *chapterRepository) Move(ctx context.Context, chapter models.Chapter, from, to models.Story) (*models.Chapter, error) { +func (r *chapterRepository) Move(ctx context.Context, chapter models.Chapter, _, to models.Story) (*models.Chapter, error) { err := psqlcore.New(r.db).UpdateChapterStoryID(ctx, psqlcore.UpdateChapterStoryIDParams{ StoryID: to.ID, ID: chapter.ID, @@ -99,7 +99,24 @@ func (r *chapterRepository) Move(ctx context.Context, chapter models.Chapter, fr } func (r *chapterRepository) Delete(ctx context.Context, chapter models.Chapter) error { - return psqlcore.New(r.db).DeleteChapter(ctx, chapter.ID) + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + q := psqlcore.New(tx) + + err = q.DeleteCommentsByChapterID(ctx, chapter.ID) + if err != nil { + return err + } + + err = q.DeleteChapter(ctx, chapter.ID) + if err != nil { + return err + } + + return tx.Commit() } func (r *chapterRepository) chapter(chapter psqlcore.StoryChapter) *models.Chapter { diff --git a/database/postgres/comments.go b/database/postgres/comments.go new file mode 100644 index 0000000..7a3525f --- /dev/null +++ b/database/postgres/comments.go @@ -0,0 +1,113 @@ +package postgres + +import ( + "context" + "database/sql" + "git.aiterp.net/rpdata/api/database/postgres/psqlcore" + "git.aiterp.net/rpdata/api/internal/generate" + "git.aiterp.net/rpdata/api/models" +) + +type commentRepository struct { + insertWithIDs bool + db *sql.DB +} + +func (r *commentRepository) Find(ctx context.Context, id string) (*models.Comment, error) { + comment, err := psqlcore.New(r.db).SelectComment(ctx, id) + if err != nil { + return nil, err + } + + return r.comment(comment), nil +} + +func (r *commentRepository) List(ctx context.Context, filter models.CommentFilter) ([]*models.Comment, error) { + params := psqlcore.SelectCommentsParams{ + ChapterID: "", + LimitSize: 10000, + } + if filter.ChapterID != nil { + params.ChapterID = *filter.ChapterID + } + if filter.Limit > 0 { + params.LimitSize = int32(filter.Limit) + } + + comments, err := psqlcore.New(r.db).SelectComments(ctx, params) + if err != nil { + return nil, err + } + + return r.comments(comments), nil +} + +func (r *commentRepository) Insert(ctx context.Context, comment models.Comment) (*models.Comment, error) { + if !r.insertWithIDs || len(comment.ID) < 8 { + comment.ID = generate.CommentID() + } + + err := psqlcore.New(r.db).InsertComment(ctx, psqlcore.InsertCommentParams{ + ID: comment.ID, + ChapterID: comment.ChapterID, + CharacterID: comment.CharacterID, + Subject: comment.Subject, + Author: comment.Author, + CharacterName: comment.CharacterID, + Source: comment.Source, + CreatedDate: comment.CreatedDate.UTC(), + FictionalDate: comment.FictionalDate.UTC(), + EditedDate: comment.EditedDate.UTC(), + }) + if err != nil { + return nil, err + } + + return &comment, nil +} + +func (r *commentRepository) Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Comment, error) { + comment.ApplyUpdate(update) + + err := psqlcore.New(r.db).UpdateComment(ctx, psqlcore.UpdateCommentParams{ + Subject: comment.Subject, + Source: comment.Source, + FictionalDate: comment.FictionalDate.UTC(), + CharacterName: comment.ChapterID, + CharacterID: comment.CharacterID, + ID: comment.ID, + }) + if err != nil { + return nil, err + } + + return &comment, nil +} + +func (r *commentRepository) Delete(ctx context.Context, comment models.Comment) error { + return psqlcore.New(r.db).DeleteComment(ctx, comment.ID) +} + +func (r *commentRepository) comment(comment psqlcore.StoryComment) *models.Comment { + return &models.Comment{ + ID: comment.ID, + ChapterID: comment.ChapterID, + Subject: comment.Subject, + Author: comment.Author, + CharacterName: comment.CharacterName, + CharacterID: comment.CharacterID, + FictionalDate: comment.FictionalDate, + CreatedDate: comment.CreatedDate, + EditedDate: comment.EditedDate, + Source: comment.Source, + } +} + +func (r *commentRepository) comments(comments []psqlcore.StoryComment) []*models.Comment { + results := make([]*models.Comment, 0, len(comments)) + for _, comment := range comments { + results = append(results, r.comment(comment)) + } + + return results +} diff --git a/database/postgres/db.go b/database/postgres/db.go index 0b9df7f..5f987a6 100644 --- a/database/postgres/db.go +++ b/database/postgres/db.go @@ -85,7 +85,7 @@ func (d *DB) Chapters() repositories.ChapterRepository { } func (d *DB) Comments() repositories.CommentRepository { - panic("implement me") + return &commentRepository{insertWithIDs: d.insertWithIDs, db: d.db} } func (d *DB) Keys() repositories.KeyRepository { diff --git a/database/postgres/migrations/20210327101320_create_table_comment.sql b/database/postgres/migrations/20210327101320_create_table_comment.sql new file mode 100644 index 0000000..c7f2e58 --- /dev/null +++ b/database/postgres/migrations/20210327101320_create_table_comment.sql @@ -0,0 +1,20 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE story_comment ( + id TEXT NOT NULL PRIMARY KEY, + chapter_id TEXT NOT NULL, + character_id TEXT NOT NULL, + subject TEXT NOT NULL, + author TEXT NOT NULL, + character_name TEXT NOT NULL, + source TEXT NOT NULL, + created_date TIMESTAMP NOT NULL, + fictional_date TIMESTAMP NOT NULL, + edited_date TIMESTAMP NOT NULL +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE story_comment; +-- +goose StatementEnd diff --git a/database/postgres/migrations/20210327101854_create_index_comment_chapter_id.sql b/database/postgres/migrations/20210327101854_create_index_comment_chapter_id.sql new file mode 100644 index 0000000..5c16b1a --- /dev/null +++ b/database/postgres/migrations/20210327101854_create_index_comment_chapter_id.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +CREATE INDEX story_comment_index_chapter_id ON story_comment (chapter_id); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP INDEX IF EXISTS story_comment_index_chapter_id; +-- +goose StatementEnd diff --git a/database/postgres/queries/chapters.sql b/database/postgres/queries/chapters.sql index 3f215aa..6f1248c 100644 --- a/database/postgres/queries/chapters.sql +++ b/database/postgres/queries/chapters.sql @@ -2,29 +2,29 @@ SELECT * FROM story_chapter WHERE id=$1::TEXT LIMIT 1; -- name: SelectChapters :many -SELECT * FROM story_chapter WHERE (sqlx.arg(story_id)::TEXT == '' OR story_id=sqlc.arg(story_id)::TEXT) ORDER BY created_date LIMIT sqlc.arg(limit_size)::INT; +SELECT * FROM story_chapter WHERE (sqlx.arg(story_id)::TEXT == '' OR story_id = @story_id::TEXT) ORDER BY created_date LIMIT @limit_size; -- name: InsertChapter :exec INSERT INTO story_chapter (id, story_id, title, author, source, created_date, fictional_date, edited_date, comment_mode, comments_locked) VALUES ( - sqlc.arg(id)::TEXT, sqlc.arg(story_id)::TEXT, sqlc.arg(title)::TEXT, sqlc.arg(author)::TEXT, sqlc.arg(source)::TEXT, - sqlc.arg(created_date)::TIMESTAMP, sqlc.arg(fictional_date)::TIMESTAMP, sqlc.arg(edited_date)::TIMESTAMP, - sqlc.arg(comment_mode)::TEXT, sqlc.arg(comments_locked)::BOOLEAN + @id::TEXT, @story_id::TEXT, @title::TEXT, @author::TEXT, @source::TEXT, + @created_date::TIMESTAMP, @fictional_date::TIMESTAMP, @edited_date::TIMESTAMP, + @comment_mode::TEXT, @comments_locked::BOOLEAN ); -- name: UpdateChapterStoryID :exec UPDATE story_chapter -SET story_id=sqlc.arg(story_id)::TEXT -WHERE id=sqlc.arg(id); +SET story_id = @story_id::TEXT +WHERE id = @id; -- name: UpdateChapter :exec UPDATE story_chapter -SET title=sqlc.arg(title), - source=sqlc.arg(source), - fictional_date=sqlc.arg(fictional_date), - comment_mode=sqlc.arg(comment_mode), - comments_locked=sqlc.arg(comments_locked) -WHERE id=sqlc.arg(id); +SET title = @title, + source = @source, + fictional_date = @fictional_date, + comment_mode = @comment_mode, + comments_locked = @comments_locked +WHERE id = @id; -- name: DeleteChapter :exec DELETE FROM story_chapter WHERE id=$1; diff --git a/database/postgres/queries/characters.sql b/database/postgres/queries/characters.sql index a41dd6b..073426b 100644 --- a/database/postgres/queries/characters.sql +++ b/database/postgres/queries/characters.sql @@ -1,50 +1,47 @@ -- name: SelectCharacterByID :one -SELECT * FROM data_character WHERE id = sqlc.arg(id)::text; +SELECT * FROM data_character WHERE id = @id::text; -- name: SelectCharacterByNick :one -SELECT * FROM data_character WHERE nicks <@ ARRAY[sqlc.arg(nick)::text]; +SELECT * FROM data_character WHERE nicks <@ ARRAY[@nick::text]; -- name: SelectCharacterByName :one -SELECT * FROM data_character WHERE name = sqlc.arg(name)::text; +SELECT * FROM data_character WHERE name = @name::text; -- name: SelectCharacters :many SELECT * FROM data_character -WHERE (sqlc.arg(filter_id)::bool = false OR id = ANY(sqlc.arg(ids)::text[])) - AND (sqlc.arg(filter_name)::bool = false OR name = ANY(sqlc.arg(names)::text[])) - AND (sqlc.arg(filter_nick)::bool = false OR nicks && (sqlc.arg(nicks)::text[])) - AND (sqlc.arg(filter_author)::bool = false OR author = sqlc.arg(author)::text) - AND (sqlc.arg(filter_search)::bool = false OR "ts_vector" @@ to_tsquery(sqlc.arg(search)::text)) -LIMIT sqlc.arg(limit_size)::int; +WHERE (@filter_id::bool = false OR id = ANY(@ids::text[])) + AND (@filter_name::bool = false OR name = ANY(@names::text[])) + AND (@filter_nick::bool = false OR nicks && (@nicks::text[])) + AND (@filter_author::bool = false OR author = @author::text) + AND (@filter_search::bool = false OR "ts_vector" @@ to_tsquery(@search::text)) +LIMIT @limit_size::int; -- name: InsertCharacter :exec INSERT INTO data_character (id, nicks, name, short_name, author, description, ts_vector) VALUES ( - sqlc.arg(id)::text, sqlc.arg(nicks)::text[], sqlc.arg(name)::text, - sqlc.arg(short_name)::text, sqlc.arg(author)::text, sqlc.arg(description)::text, + @id::text, @nicks::text[], @name::text, + @short_name::text, @author::text, @description::text, to_tsvector( - 'english', - sqlc.arg(name)::text || ' ' || sqlc.arg(description)::text || ' ' || sqlc.arg(author)::text || ' ' - || immutable_array_to_string( - sqlc.arg(nicks)::text[], ' ' - ) + 'english', @name::text || ' ' || @description::text || ' ' || @author::text || ' ' + || immutable_array_to_string(@nicks::text[], ' ') ) ); -- name: UpdateCharacter :exec UPDATE data_character -SET name=sqlc.arg(name)::text, - short_name=sqlc.arg(short_name)::text, - description=sqlc.arg(description)::text -WHERE id=sqlc.arg(id)::text; +SET name = @name::text, + short_name = @short_name::text, + description = @description::text +WHERE id = @id::text; -- name: AddCharacterNick :exec UPDATE data_character -SET nicks=append(nicks, sqlc.arg(nick)::text) -WHERE id=sqlc.arg(id)::text; +SET nicks=array_append(nicks, @nick::text) +WHERE id = @id::text; -- name: RemoveCharacterNick :exec UPDATE data_character -SET nicks=array_remove(nicks, sqlc.arg(nick)::text) -WHERE id=sqlc.arg(id)::text; +SET nicks=array_remove(nicks, @nick::text) +WHERE id = @id::text; -- name: DeleteCharacter :exec DELETE FROM data_character WHERE id=$1; \ No newline at end of file diff --git a/database/postgres/queries/comments.sql b/database/postgres/queries/comments.sql new file mode 100644 index 0000000..a5f6635 --- /dev/null +++ b/database/postgres/queries/comments.sql @@ -0,0 +1,34 @@ +-- name: SelectComment :one +SELECT * FROM story_comment WHERE id=$1; + +-- name: InsertComment :exec +INSERT INTO story_comment (id, chapter_id, character_id, subject, author, character_name, source, created_date, fictional_date, edited_date) +VALUES ( + @id, @chapter_id, @character_id, + @subject, @author, @character_name, @source, + @created_date, @fictional_date, @edited_date +); + +-- name: UpdateComment :exec +UPDATE story_comment +SET subject = @subject, + source = @source, + fictional_date = @fictional_date, + character_name = @character_name, + character_id = @character_id +WHERE id = @id; + +-- name: SelectComments :many +SELECT * FROM story_comment WHERE (@chapter_id = '' OR chapter_id = @chapter_id) LIMIT @limit_size; + +-- name: DeleteComment :exec +DELETE FROM story_comment WHERE id = $1; + +-- name: DeleteCommentsByChapterID :exec +DELETE FROM story_comment WHERE chapter_id = $1; + +-- name: DeleteCommentsByStoryID :exec +DELETE FROM story_comment +WHERE chapter_id IN ( + SELECT id FROM story_chapter WHERE story_id = $1 +); \ No newline at end of file diff --git a/database/postgres/stories.go b/database/postgres/stories.go index 2dc3556..f31d1e3 100644 --- a/database/postgres/stories.go +++ b/database/postgres/stories.go @@ -193,6 +193,11 @@ func (r *storyRepository) Delete(ctx context.Context, story models.Story) error return err } + err = q.DeleteCommentsByStoryID(ctx, story.ID) + if err != nil { + return err + } + err = q.DeleteChaptersByStoryID(ctx, story.ID) if err != nil { return err diff --git a/models/comment.go b/models/comment.go index a1a083d..7ed3132 100644 --- a/models/comment.go +++ b/models/comment.go @@ -16,6 +16,24 @@ type Comment struct { Source string `bson:"source"` } +func (comment *Comment) ApplyUpdate(update CommentUpdate) { + if update.Source != nil { + comment.Source = *update.Source + } + if update.CharacterName != nil { + comment.CharacterName = *update.CharacterName + } + if update.CharacterID != nil { + comment.CharacterID = *update.CharacterID + } + if update.Subject != nil { + comment.Subject = *update.Subject + } + if update.FictionalDate != nil { + comment.FictionalDate = *update.FictionalDate + } +} + // IsChangeObject is an interface implementation to identify it as a valid // ChangeObject in GQL. func (*Comment) IsChangeObject() {