From 525191a0f9fe5aa812b61411332610a223cf0929 Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Fri, 26 Mar 2021 15:47:18 +0100 Subject: [PATCH] tweak tags and add story repository. --- cmd/rpdata-restore/main.go | 4 + database/postgres/character.go | 1 - database/postgres/db.go | 2 +- database/postgres/psqlcore/stories.sql.go | 87 ++++++++++++ database/postgres/psqlcore/tags.sql.go | 93 ++++++++++-- database/postgres/queries/changes.sql | 1 + database/postgres/queries/channels.sql | 1 + database/postgres/queries/stories.sql | 24 ++++ database/postgres/queries/tags.sql | 28 +++- database/postgres/stories.go | 163 ++++++++++++++++++---- database/postgres/tags.go | 22 ++- go.sum | 1 - graph2/complexity.go | 3 +- models/story.go | 29 +++- services/characters.go | 7 + services/stories.go | 14 ++ 16 files changed, 418 insertions(+), 62 deletions(-) diff --git a/cmd/rpdata-restore/main.go b/cmd/rpdata-restore/main.go index af0847c..ddb42d6 100644 --- a/cmd/rpdata-restore/main.go +++ b/cmd/rpdata-restore/main.go @@ -83,6 +83,10 @@ func main() { hideList := make(map[string]bool) + if parts[1] != "story" { + continue + } + log.Println("Loaded", parts[1], parts[2], "from archive.") switch parts[1] { diff --git a/database/postgres/character.go b/database/postgres/character.go index 14d0452..9124165 100644 --- a/database/postgres/character.go +++ b/database/postgres/character.go @@ -108,7 +108,6 @@ func (r *characterRepository) Insert(ctx context.Context, character models.Chara return nil, err } defer func() { _ = tx.Rollback() }() - q := psqlcore.New(tx) if !r.insertWithIDs || character.ID == "" { diff --git a/database/postgres/db.go b/database/postgres/db.go index c9aadda..a230f16 100644 --- a/database/postgres/db.go +++ b/database/postgres/db.go @@ -77,7 +77,7 @@ func (d *DB) Posts() repositories.PostRepository { } func (d *DB) Stories() repositories.StoryRepository { - panic("implement me") + return &storyRepository{insertWithIDs: d.insertWithIDs, db: d.db} } func (d *DB) Chapters() repositories.ChapterRepository { diff --git a/database/postgres/psqlcore/stories.sql.go b/database/postgres/psqlcore/stories.sql.go index 0f3a37f..3b4bfce 100644 --- a/database/postgres/psqlcore/stories.sql.go +++ b/database/postgres/psqlcore/stories.sql.go @@ -10,6 +10,53 @@ import ( "github.com/lib/pq" ) +const deleteStory = `-- name: DeleteStory :exec +DELETE FROM story WHERE id=$1 +` + +func (q *Queries) DeleteStory(ctx context.Context, id string) error { + _, err := q.db.ExecContext(ctx, deleteStory, id) + return err +} + +const insertStory = `-- name: InsertStory :exec +INSERT INTO story (id, author, name, category, open, listed, sort_by_fictional_date, created_date, fictional_date, updated_date) +VALUES ( + $1::text, $2::text, $3::text, $4::text, + $5::boolean, $6::boolean, $7::boolean, + $8::timestamp, $9::timestamp, $10::timestamp +) +` + +type InsertStoryParams struct { + ID string `json:"id"` + Author string `json:"author"` + Name string `json:"name"` + Category string `json:"category"` + Open bool `json:"open"` + Listed bool `json:"listed"` + SortByFictionalDate bool `json:"sort_by_fictional_date"` + CreatedDate time.Time `json:"created_date"` + FictionalDate time.Time `json:"fictional_date"` + UpdatedDate time.Time `json:"updated_date"` +} + +func (q *Queries) InsertStory(ctx context.Context, arg InsertStoryParams) error { + _, err := q.db.ExecContext(ctx, insertStory, + arg.ID, + arg.Author, + arg.Name, + arg.Category, + arg.Open, + arg.Listed, + arg.SortByFictionalDate, + arg.CreatedDate, + arg.FictionalDate, + arg.UpdatedDate, + ) + return err +} + const selectStories = `-- name: SelectStories :many SELECT id, author, name, category, open, listed, sort_by_fictional_date, created_date, fictional_date, updated_date FROM story WHERE ($1::bool = false OR id = ANY($2::text[])) @@ -111,3 +158,43 @@ func (q *Queries) SelectStory(ctx context.Context, id string) (Story, error) { ) return i, err } + +const updateStory = `-- name: UpdateStory :exec +UPDATE story +SET name=$1, + category=$2, + author=$3, + open=$4, + listed=$5, + fictional_date=$6, + updated_date=$7, + sort_by_fictional_date=$8 +WHERE id=$9 +` + +type UpdateStoryParams struct { + Name string `json:"name"` + Category string `json:"category"` + Author string `json:"author"` + Open bool `json:"open"` + Listed bool `json:"listed"` + FictionalDate time.Time `json:"fictional_date"` + UpdatedDate time.Time `json:"updated_date"` + SortByFictionalDate bool `json:"sort_by_fictional_date"` + ID string `json:"id"` +} + +func (q *Queries) UpdateStory(ctx context.Context, arg UpdateStoryParams) error { + _, err := q.db.ExecContext(ctx, updateStory, + arg.Name, + arg.Category, + arg.Author, + arg.Open, + arg.Listed, + arg.FictionalDate, + arg.UpdatedDate, + arg.SortByFictionalDate, + arg.ID, + ) + return err +} diff --git a/database/postgres/psqlcore/tags.sql.go b/database/postgres/psqlcore/tags.sql.go index 477d5f6..79422d1 100644 --- a/database/postgres/psqlcore/tags.sql.go +++ b/database/postgres/psqlcore/tags.sql.go @@ -43,8 +43,26 @@ func (q *Queries) ClearTagsByTarget(ctx context.Context, arg ClearTagsByTargetPa return err } +const clearTagsByTargetLike = `-- name: ClearTagsByTargetLike :exec +DELETE FROM common_tag +WHERE target_kind=$1::TEXT + AND target_id=$2::TEXT + AND tag LIKE $3::TEXT +` + +type ClearTagsByTargetLikeParams struct { + TargetKind string `json:"target_kind"` + TargetID string `json:"target_id"` + TagLike string `json:"tag_like"` +} + +func (q *Queries) ClearTagsByTargetLike(ctx context.Context, arg ClearTagsByTargetLikeParams) error { + _, err := q.db.ExecContext(ctx, clearTagsByTargetLike, arg.TargetKind, arg.TargetID, arg.TagLike) + return err +} + const selectDistinctTags = `-- name: SelectDistinctTags :many -SELECT DISTINCT tag FROM common_tag +SELECT DISTINCT tag FROM common_tag ORDER BY tag ` func (q *Queries) SelectDistinctTags(ctx context.Context) ([]string, error) { @@ -70,12 +88,12 @@ func (q *Queries) SelectDistinctTags(ctx context.Context) ([]string, error) { return items, nil } -const selectDistinctTagsByKind = `-- name: SelectDistinctTagsByKind :many -SELECT DISTINCT tag FROM common_tag WHERE tag LIKE '$1::text%' +const selectDistinctTagsLike = `-- name: SelectDistinctTagsLike :many +SELECT DISTINCT tag FROM common_tag WHERE tag LIKE $1::text ORDER BY tag ` -func (q *Queries) SelectDistinctTagsByKind(ctx context.Context) ([]string, error) { - rows, err := q.db.QueryContext(ctx, selectDistinctTagsByKind) +func (q *Queries) SelectDistinctTagsLike(ctx context.Context, dollar_1 string) ([]string, error) { + rows, err := q.db.QueryContext(ctx, selectDistinctTagsLike, dollar_1) if err != nil { return nil, err } @@ -98,7 +116,7 @@ func (q *Queries) SelectDistinctTagsByKind(ctx context.Context) ([]string, error } const selectTagsByTag = `-- name: SelectTagsByTag :many -SELECT tag, target_kind, target_id FROM common_tag WHERE tag = $1::text +SELECT tag, target_kind, target_id FROM common_tag WHERE tag = $1::text ORDER BY tag ` func (q *Queries) SelectTagsByTag(ctx context.Context, tagName string) ([]CommonTag, error) { @@ -125,7 +143,7 @@ func (q *Queries) SelectTagsByTag(ctx context.Context, tagName string) ([]Common } const selectTagsByTags = `-- name: SelectTagsByTags :many -SELECT tag, target_kind, target_id FROM common_tag WHERE tag = ANY($1::text[]) +SELECT tag, target_kind, target_id FROM common_tag WHERE tag = ANY($1::text[]) ORDER BY tag ` func (q *Queries) SelectTagsByTags(ctx context.Context, tagNames []string) ([]CommonTag, error) { @@ -152,16 +170,16 @@ func (q *Queries) SelectTagsByTags(ctx context.Context, tagNames []string) ([]Co } const selectTagsByTarget = `-- name: SelectTagsByTarget :many -SELECT tag, target_kind, target_id FROM common_tag WHERE target_kind = $1 AND target_id = $2::text +SELECT tag, target_kind, target_id FROM common_tag WHERE target_kind = $1 AND target_id = $2::text ORDER BY tag ` type SelectTagsByTargetParams struct { TargetKind string `json:"target_kind"` - Column2 string `json:"column_2"` + TargetID string `json:"target_id"` } func (q *Queries) SelectTagsByTarget(ctx context.Context, arg SelectTagsByTargetParams) ([]CommonTag, error) { - rows, err := q.db.QueryContext(ctx, selectTagsByTarget, arg.TargetKind, arg.Column2) + rows, err := q.db.QueryContext(ctx, selectTagsByTarget, arg.TargetKind, arg.TargetID) if err != nil { return nil, err } @@ -184,16 +202,16 @@ func (q *Queries) SelectTagsByTarget(ctx context.Context, arg SelectTagsByTarget } const selectTagsByTargets = `-- name: SelectTagsByTargets :many -SELECT tag, target_kind, target_id FROM common_tag WHERE target_kind = $1 AND target_id = ANY($2::text[]) +SELECT tag, target_kind, target_id FROM common_tag WHERE target_kind = $1 AND target_id = ANY($2::text[]) ORDER BY tag ` type SelectTagsByTargetsParams struct { TargetKind string `json:"target_kind"` - Column2 []string `json:"column_2"` + TargetIds []string `json:"target_ids"` } func (q *Queries) SelectTagsByTargets(ctx context.Context, arg SelectTagsByTargetsParams) ([]CommonTag, error) { - rows, err := q.db.QueryContext(ctx, selectTagsByTargets, arg.TargetKind, pq.Array(arg.Column2)) + rows, err := q.db.QueryContext(ctx, selectTagsByTargets, arg.TargetKind, pq.Array(arg.TargetIds)) if err != nil { return nil, err } @@ -215,6 +233,38 @@ func (q *Queries) SelectTagsByTargets(ctx context.Context, arg SelectTagsByTarge return items, nil } +const selectTargetsByTags = `-- name: SelectTargetsByTags :many +SELECT DISTINCT(target_id) FROM common_tag WHERE tag = ANY($1::text[]) AND target_kind = $2::text ORDER BY tag +` + +type SelectTargetsByTagsParams struct { + TagNames []string `json:"tag_names"` + TargetKind string `json:"target_kind"` +} + +func (q *Queries) SelectTargetsByTags(ctx context.Context, arg SelectTargetsByTagsParams) ([]string, error) { + rows, err := q.db.QueryContext(ctx, selectTargetsByTags, pq.Array(arg.TagNames), arg.TargetKind) + if err != nil { + return nil, err + } + defer rows.Close() + items := []string{} + for rows.Next() { + var target_id string + if err := rows.Scan(&target_id); err != nil { + return nil, err + } + items = append(items, target_id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const setTag = `-- name: SetTag :exec INSERT INTO common_tag (tag, target_kind, target_id) VALUES ( @@ -233,3 +283,20 @@ func (q *Queries) SetTag(ctx context.Context, arg SetTagParams) error { _, err := q.db.ExecContext(ctx, setTag, arg.Tag, arg.TargetKind, arg.TargetID) return err } + +const setTags = `-- name: SetTags :exec +INSERT INTO common_tag (tag, target_kind, target_id) +SELECT unnest($1::TEXT[]), $2::TEXT as target_kind, $3::TEXT as target_id +ON CONFLICT DO NOTHING +` + +type SetTagsParams struct { + Tags []string `json:"tags"` + TargetKind string `json:"target_kind"` + TargetID string `json:"target_id"` +} + +func (q *Queries) SetTags(ctx context.Context, arg SetTagsParams) error { + _, err := q.db.ExecContext(ctx, setTags, pq.Array(arg.Tags), arg.TargetKind, arg.TargetID) + return err +} diff --git a/database/postgres/queries/changes.sql b/database/postgres/queries/changes.sql index 06d8459..828a58d 100644 --- a/database/postgres/queries/changes.sql +++ b/database/postgres/queries/changes.sql @@ -7,6 +7,7 @@ WHERE (sqlc.arg(filter_keys)::bool = false OR keys && (sqlc.arg(keys)::text[])) AND (sqlc.arg(filter_earliest_date)::bool = false OR date >= sqlc.arg(earliest_date)::timestamp) AND (sqlc.arg(filter_latest_date)::bool = false OR date <= sqlc.arg(latest_date)::timestamp) AND (sqlc.arg(filter_author)::bool = false OR author = sqlc.arg(author)::text) +ORDER BY date DESC LIMIT sqlc.arg(limit_size)::int; -- name: InsertChange :exec diff --git a/database/postgres/queries/channels.sql b/database/postgres/queries/channels.sql index 99e3768..6936357 100644 --- a/database/postgres/queries/channels.sql +++ b/database/postgres/queries/channels.sql @@ -15,6 +15,7 @@ WHERE (sqlc.arg(filter_name)::bool = false OR name = ANY(sqlc.arg(names)::text[] AND (sqlc.arg(filter_logged)::bool = false OR logged = sqlc.arg(logged)) AND (sqlc.arg(filter_event_name)::bool = false OR event_name = sqlc.arg(event_name)) AND (sqlc.arg(filter_location_name)::bool = false OR location_name = sqlc.arg(location_name)) +ORDER BY name LIMIT sqlc.arg(limit_size)::int; -- name: UpdateChannel :exec diff --git a/database/postgres/queries/stories.sql b/database/postgres/queries/stories.sql index eeefa54..2f03aeb 100644 --- a/database/postgres/queries/stories.sql +++ b/database/postgres/queries/stories.sql @@ -10,4 +10,28 @@ WHERE (sqlc.arg(filter_id)::bool = false OR id = ANY(sqlc.arg(ids)::text[])) AND (sqlc.arg(filter_category)::bool = false OR category = sqlc.arg(category)::text) AND (sqlc.arg(filter_open)::bool = false OR open = sqlc.arg(open)::bool) AND (sqlc.arg(filter_unlisted)::bool = false OR unlisted = sqlc.arg(unlisted)::bool) +ORDER BY updated_date LIMIT sqlc.arg(limit_size)::int; + +-- name: InsertStory :exec +INSERT INTO story (id, author, name, category, open, listed, sort_by_fictional_date, created_date, fictional_date, updated_date) +VALUES ( + sqlc.arg(id)::text, sqlc.arg(author)::text, sqlc.arg(name)::text, sqlc.arg(category)::text, + sqlc.arg(open)::boolean, sqlc.arg(listed)::boolean, sqlc.arg(sort_by_fictional_date)::boolean, + sqlc.arg(created_date)::timestamp, sqlc.arg(fictional_date)::timestamp, sqlc.arg(updated_date)::timestamp +); + +-- name: UpdateStory :exec +UPDATE story +SET name=sqlc.arg(name), + category=sqlc.arg(category), + author=sqlc.arg(author), + open=sqlc.arg(open), + listed=sqlc.arg(listed), + fictional_date=sqlc.arg(fictional_date), + updated_date=sqlc.arg(updated_date), + sort_by_fictional_date=sqlc.arg(sort_by_fictional_date) +WHERE id=sqlc.arg(id); + +-- name: DeleteStory :exec +DELETE FROM story WHERE id=$1; \ No newline at end of file diff --git a/database/postgres/queries/tags.sql b/database/postgres/queries/tags.sql index 1c9f3be..3f7fd9d 100644 --- a/database/postgres/queries/tags.sql +++ b/database/postgres/queries/tags.sql @@ -5,31 +5,45 @@ VALUES ( ) ON CONFLICT DO NOTHING; +-- name: SetTags :exec +INSERT INTO common_tag (tag, target_kind, target_id) +SELECT unnest(sqlc.arg(tags)::TEXT[]), sqlc.arg(target_kind)::TEXT as target_kind, sqlc.arg(target_id)::TEXT as target_id +ON CONFLICT DO NOTHING; + -- name: ClearTag :exec DELETE FROM common_tag WHERE tag=sqlc.arg(tag)::TEXT AND target_kind=sqlc.arg(target_kind)::TEXT AND target_id=sqlc.arg(target_id)::TEXT; +-- name: ClearTagsByTargetLike :exec +DELETE FROM common_tag +WHERE target_kind=sqlc.arg(target_kind)::TEXT + AND target_id=sqlc.arg(target_id)::TEXT + AND tag LIKE sqlc.arg(tag_like)::TEXT; + -- name: ClearTagsByTarget :exec DELETE FROM common_tag WHERE target_kind=sqlc.arg(target_kind)::TEXT AND target_id=sqlc.arg(target_id)::TEXT; -- name: SelectDistinctTags :many -SELECT DISTINCT tag FROM common_tag; +SELECT DISTINCT tag FROM common_tag ORDER BY tag; --- name: SelectDistinctTagsByKind :many -SELECT DISTINCT tag FROM common_tag WHERE tag LIKE '$1::text%'; +-- name: SelectDistinctTagsLike :many +SELECT DISTINCT tag FROM common_tag WHERE tag LIKE $1::text ORDER BY tag; -- name: SelectTagsByTag :many -SELECT * FROM common_tag WHERE tag = sqlc.arg(tag_name)::text; +SELECT * FROM common_tag WHERE tag = sqlc.arg(tag_name)::text ORDER BY tag; -- name: SelectTagsByTags :many -SELECT * FROM common_tag WHERE tag = ANY(sqlc.arg(tag_names)::text[]); +SELECT * FROM common_tag WHERE tag = ANY(sqlc.arg(tag_names)::text[]) ORDER BY tag; + +-- name: SelectTargetsByTags :many +SELECT DISTINCT(target_id) FROM common_tag WHERE tag = ANY(sqlc.arg(tag_names)::text[]) AND target_kind = sqlc.arg(target_kind)::text ORDER BY tag; -- name: SelectTagsByTarget :many -SELECT * FROM common_tag WHERE target_kind = $1 AND target_id = $2::text; +SELECT * FROM common_tag WHERE target_kind = sqlc.arg(target_kind) AND target_id = sqlc.arg(target_id)::text ORDER BY tag; -- name: SelectTagsByTargets :many -SELECT * FROM common_tag WHERE target_kind = $1 AND target_id = ANY($2::text[]); +SELECT * FROM common_tag WHERE target_kind = sqlc.arg(target_kind) AND target_id = ANY(sqlc.arg(target_ids)::text[]) ORDER BY tag; diff --git a/database/postgres/stories.go b/database/postgres/stories.go index 273fb61..2d00dd7 100644 --- a/database/postgres/stories.go +++ b/database/postgres/stories.go @@ -4,6 +4,7 @@ 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" ) @@ -35,72 +36,172 @@ func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) ( params := psqlcore.SelectStoriesParams{LimitSize: 100} if len(filter.Tags) > 0 { - params.FilterID = true - if len(filter.Tags) == 1 { - tags, err := q.SelectTagsByKindName(ctx, psqlcore.SelectTagsByKindNameParams{ - Kind: string(filter.Tags[0].Kind), - Name: filter.Tags[0].Name, - }) - if err != nil && err != sql.ErrNoRows { - return nil, err - } - - for _, tag := range tags { - params.Ids = append(params.Ids, tag.TargetID) - } - } else { - + targets, err := q.SelectTargetsByTags(ctx, psqlcore.SelectTargetsByTagsParams{ + TagNames: models.EncodeTagArray(filter.Tags), + TargetKind: "Story", + }) + if err != nil && err != sql.ErrNoRows { + return nil, err } if len(params.Ids) == 0 { return []*models.Story{}, nil } + + params.FilterID = true + params.Ids = targets + } + if filter.Author != nil { + params.FilterAuthor = true + params.Author = *filter.Author + } + if filter.Category != nil { + params.FilterCategory = true + params.Category = string(*filter.Category) + } + if !filter.EarliestFictionalDate.IsZero() { + params.FilterEarlistFictionalDate = true + params.EarliestFictionalDate = filter.EarliestFictionalDate.UTC() + } + if !filter.LatestFictionalDate.IsZero() { + params.FilterLastestFictionalDate = true + params.LatestFictionalDate = filter.LatestFictionalDate.UTC() + } + if filter.Open != nil { + params.FilterOpen = true + params.Open = *filter.Open + } + if filter.Unlisted != nil { + params.FilterUnlisted = true + params.Unlisted = *filter.Unlisted + } + if filter.Limit <= 0 { + params.LimitSize = 1000 + } + + stories, err := q.SelectStories(ctx, params) + if err != nil { + return nil, err } - if filter.Limit > 0 { - params.LimitSize = 1000000 + + targetIDs := make([]string, len(stories)) + for i, story := range stories { + targetIDs[i] = story.ID } + tags, err := q.SelectTagsByTargets(ctx, psqlcore.SelectTagsByTargetsParams{ + TargetKind: "Story", + TargetIds: targetIDs, + }) - panic("implement me") + return r.stories(stories, tags), nil } func (r *storyRepository) Insert(ctx context.Context, story models.Story) (*models.Story, error) { - panic("implement me") + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + q := psqlcore.New(tx) + + if !r.insertWithIDs || len(story.ID) < 8 { + story.ID = generate.StoryID() + } + + if story.UpdatedDate.Before(story.CreatedDate) { + story.UpdatedDate = story.CreatedDate + } + + err = q.InsertStory(ctx, psqlcore.InsertStoryParams{ + ID: story.ID, + Author: story.Author, + Name: story.Name, + Category: string(story.Category), + Open: story.Open, + Listed: story.Listed, + SortByFictionalDate: story.SortByFictionalDate, + CreatedDate: story.CreatedDate.UTC(), + FictionalDate: story.FictionalDate.UTC(), + UpdatedDate: story.UpdatedDate.UTC(), + }) + if err != nil { + return nil, err + } + + // This is inefficient, but tags are very rarely added before the story is submitted. + err = q.SetTags(ctx, psqlcore.SetTagsParams{ + Tags: models.EncodeTagArray(story.Tags), + TargetKind: "Story", + TargetID: story.ID, + }) + if err != nil { + return nil, err + } + return &story, tx.Commit() } func (r *storyRepository) Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error) { - panic("implement me") + story.ApplyUpdate(update) + + err := psqlcore.New(r.db).UpdateStory(ctx, psqlcore.UpdateStoryParams{ + Name: story.Name, + Category: string(story.Category), + Author: story.Author, + Open: story.Open, + Listed: story.Listed, + FictionalDate: story.FictionalDate.UTC(), + UpdatedDate: story.UpdatedDate.UTC(), + SortByFictionalDate: story.SortByFictionalDate, + ID: story.ID, + }) + if err != nil { + return nil, err + } + + return &story, nil } func (r *storyRepository) AddTag(ctx context.Context, story models.Story, tag models.Tag) error { return psqlcore.New(r.db).SetTag(ctx, psqlcore.SetTagParams{ - Kind: string(tag.Kind), - Name: tag.Name, TargetKind: "Story", TargetID: story.ID, + Tag: tag.String(), }) } func (r *storyRepository) RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error { return psqlcore.New(r.db).ClearTag(ctx, psqlcore.ClearTagParams{ - Kind: string(tag.Kind), - Name: tag.Name, TargetKind: "Story", TargetID: story.ID, + Tag: tag.String(), }) } func (r *storyRepository) Delete(ctx context.Context, story models.Story) error { - panic("implement me") + q := psqlcore.New(r.db) + + err := q.ClearTagsByTarget(ctx, psqlcore.ClearTagsByTargetParams{ + TargetKind: "Story", + TargetID: story.ID, + }) + if err != nil { + return err + } + + return q.DeleteStory(ctx, story.ID) } func (r *storyRepository) story(story psqlcore.Story, tags []psqlcore.CommonTag) *models.Story { - tags2 := make([]models.Tag, 0, 8) + storyTags := make([]models.Tag, 0, 8) for _, tag := range tags { if tag.TargetKind == "Story" && tag.TargetID == story.ID { - tags2 = append(tags2, models.Tag{ - Kind: models.TagKind(tag.Kind), - Name: tag.Name, - }) + newTag := models.Tag{} + err := newTag.Decode(tag.Tag) + if err != nil { + continue + } + + storyTags = append(storyTags, newTag) } } @@ -111,7 +212,7 @@ func (r *storyRepository) story(story psqlcore.Story, tags []psqlcore.CommonTag) Category: models.StoryCategory(story.Category), Open: story.Open, Listed: story.Listed, - Tags: tags2, + Tags: storyTags, CreatedDate: story.CreatedDate, FictionalDate: story.FictionalDate, UpdatedDate: story.UpdatedDate, diff --git a/database/postgres/tags.go b/database/postgres/tags.go index 51f7f41..dfa6313 100644 --- a/database/postgres/tags.go +++ b/database/postgres/tags.go @@ -13,19 +13,25 @@ type tagRepository struct { } func (r *tagRepository) Find(ctx context.Context, kind models.TagKind, name string) (*models.Tag, error) { - tag, err := psqlcore.New(r.db).SelectTagsByTag(ctx, fmt.Sprintf("%s:%s", kind, nmae)) + tag, err := psqlcore.New(r.db).SelectTagsByTag(ctx, fmt.Sprintf("%s:%s", kind, name)) if err != nil { return nil, err } - return &models.Tag{Kind: models.TagKind(tag.Kind), Name: tag.Name}, nil + tag2 := &models.Tag{} + err = tag2.Decode(tag[0].Tag) + if err != nil { + return nil, err + } + + return tag2, nil } func (r *tagRepository) List(ctx context.Context, filter models.TagFilter) ([]*models.Tag, error) { var tags []string var err error if filter.Kind != nil { - tags, err = psqlcore.New(r.db).SelectDistinctTagsByKind(ctx, string(*filter.Kind)) + tags, err = psqlcore.New(r.db).SelectDistinctTagsLike(ctx, string(*filter.Kind)+"%") } else { tags, err = psqlcore.New(r.db).SelectDistinctTags(ctx) } @@ -36,11 +42,15 @@ func (r *tagRepository) List(ctx context.Context, filter models.TagFilter) ([]*m return models.DecodeTagArray(tags) } -func modelsTagsFromDataTags(tags []psqlcore.CommonTag) []*models.Tag { +func modelsTagsFromDataTags(tags []psqlcore.CommonTag) ([]*models.Tag, error) { results := make([]*models.Tag, len(tags)) for i, tag := range tags { - results[i] = &models.Tag{Kind: models.TagKind(tag.Kind), Name: tag.Name} + results[i] = &models.Tag{} + err := results[i].Decode(tag.Tag) + if err != nil { + return nil, err + } } - return results + return results, nil } diff --git a/go.sum b/go.sum index 48163a2..50696fc 100644 --- a/go.sum +++ b/go.sum @@ -201,7 +201,6 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/graph2/complexity.go b/graph2/complexity.go index d60d3ca..1c3cea2 100644 --- a/graph2/complexity.go +++ b/graph2/complexity.go @@ -6,6 +6,7 @@ import ( ) func complexity() (cr graphcore.ComplexityRoot) { + tinySubListComplexity := 2 subListComplexity := 25 bigSublistComplexity := 75 hugeSublistComplexity := 100 @@ -175,7 +176,7 @@ func complexity() (cr graphcore.ComplexityRoot) { return childComplexity + bigSublistComplexity } cr.Story.Tags = func(childComplexity int) int { - return childComplexity + subListComplexity + return childComplexity + tinySubListComplexity } cr.Chapter.Comments = func(childComplexity int, limit *int) int { diff --git a/models/story.go b/models/story.go index 48206cc..36848e7 100644 --- a/models/story.go +++ b/models/story.go @@ -18,9 +18,36 @@ type Story struct { SortByFictionalDate bool `bson:"sortByFictionalDate"` } +func (story *Story) ApplyUpdate(update StoryUpdate) { + if update.Name != nil { + story.Name = *update.Name + } + if update.Category != nil { + story.Category = *update.Category + } + if update.Author != nil { + story.Author = *update.Author + } + if update.Open != nil { + story.Open = *update.Open + } + if update.Listed != nil { + story.Listed = *update.Listed + } + if update.SortByFictionalDate != nil { + story.SortByFictionalDate = *update.SortByFictionalDate + } + if update.FictionalDate != nil { + story.FictionalDate = *update.FictionalDate + } + if update.UpdatedDate != nil { + story.UpdatedDate = *update.UpdatedDate + } +} + // IsChangeObject is an interface implementation to identify it as a valid // ChangeObject in GQL. -func (*Story) IsChangeObject() { +func (_ *Story) IsChangeObject() { panic("this method is a dummy, and so is its caller") } diff --git a/services/characters.go b/services/characters.go index aeea830..e9aa90d 100644 --- a/services/characters.go +++ b/services/characters.go @@ -61,6 +61,13 @@ func (s *CharacterService) List(ctx context.Context, filter models.CharacterFilt } sort.Slice(characters, func(i, j int) bool { + if len(characters[i].ID) > len(characters[j].ID) { + return true + } + if len(characters[i].ID) < len(characters[j].ID) { + return false + } + return strings.Compare(characters[i].ID, characters[j].ID) < 0 }) diff --git a/services/stories.go b/services/stories.go index 550eb75..9d173f3 100644 --- a/services/stories.go +++ b/services/stories.go @@ -34,6 +34,20 @@ func (s *StoryService) FindComment(ctx context.Context, id string) (*models.Comm } func (s *StoryService) ListStories(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) { + if filter.Unlisted != nil && *filter.Unlisted { + token := s.authService.TokenFromContext(ctx) + if !token.Authenticated() { + return nil, errors.New("you cannot view unlisted stories") + } + if !token.Permitted("story.unlisted") { + if filter.Author == nil { + filter.Author = &token.UserID + } else if *filter.Author != token.UserID { + return nil, errors.New("you cannot view your own unlisted stories") + } + } + } + return s.stories.List(ctx, filter) }