fix repository bugs and make text searching work more like it did in the past.
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -3,20 +3,18 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.aiterp.net/rpdata/api/space"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.aiterp.net/rpdata/api/database"
|
"git.aiterp.net/rpdata/api/database"
|
||||||
"git.aiterp.net/rpdata/api/graph2"
|
"git.aiterp.net/rpdata/api/graph2"
|
||||||
"git.aiterp.net/rpdata/api/internal/config"
|
"git.aiterp.net/rpdata/api/internal/config"
|
||||||
"git.aiterp.net/rpdata/api/internal/instrumentation"
|
"git.aiterp.net/rpdata/api/internal/instrumentation"
|
||||||
"git.aiterp.net/rpdata/api/models"
|
"git.aiterp.net/rpdata/api/models"
|
||||||
"git.aiterp.net/rpdata/api/services"
|
"git.aiterp.net/rpdata/api/services"
|
||||||
|
"git.aiterp.net/rpdata/api/space"
|
||||||
"github.com/99designs/gqlgen/handler"
|
"github.com/99designs/gqlgen/handler"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -54,11 +52,6 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Refresh Characters:", err)
|
log.Println("Refresh Characters:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = serviceBundle.Logs.FixImportDateBug(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Fix date bug:", err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go logListedChanges(serviceBundle.Changes)
|
go logListedChanges(serviceBundle.Changes)
|
||||||
@@ -112,13 +105,6 @@ func queryHandler(services *services.Bundle) http.HandlerFunc {
|
|||||||
handler.ResolverMiddleware(instrumentation.ResolverMiddleware()),
|
handler.ResolverMiddleware(instrumentation.ResolverMiddleware()),
|
||||||
)
|
)
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// >_>
|
|
||||||
if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer of the curse") {
|
|
||||||
w.Header().Set("X-Emerald-Herald", "Seek souls. Larger, more powerful souls.")
|
|
||||||
w.Header().Add("X-Emerald-Herald", "Seek the king, that is the only way.")
|
|
||||||
w.Header().Add("X-Emerald-Herald", "Lest this land swallow you whole... As it has so many others.")
|
|
||||||
}
|
|
||||||
|
|
||||||
r = services.Auth.RequestWithToken(r)
|
r = services.Auth.RequestWithToken(r)
|
||||||
|
|
||||||
handler.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|||||||
@@ -18,26 +18,6 @@ type characterRepository struct {
|
|||||||
db *sql.DB
|
db *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *characterRepository) character(row psqlcore.DataCharacter) *models.Character {
|
|
||||||
return &models.Character{
|
|
||||||
ID: strings.Trim(row.ID, " "),
|
|
||||||
Nicks: row.Nicks,
|
|
||||||
Name: row.Name,
|
|
||||||
ShortName: row.ShortName,
|
|
||||||
Author: row.Author,
|
|
||||||
Description: row.Description,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *characterRepository) characters(rows []psqlcore.DataCharacter) []*models.Character {
|
|
||||||
results := make([]*models.Character, 0, len(rows))
|
|
||||||
for _, row := range rows {
|
|
||||||
results = append(results, r.character(row))
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *characterRepository) Find(ctx context.Context, id string) (*models.Character, error) {
|
func (r *characterRepository) Find(ctx context.Context, id string) (*models.Character, error) {
|
||||||
row, err := psqlcore.New(r.db).SelectCharacterByID(ctx, id)
|
row, err := psqlcore.New(r.db).SelectCharacterByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,7 +33,7 @@ func (r *characterRepository) FindNick(ctx context.Context, nick string) (*model
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.character(row), nil
|
return r.character(psqlcore.SelectCharacterByIDRow(row)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *characterRepository) FindName(ctx context.Context, name string) (*models.Character, error) {
|
func (r *characterRepository) FindName(ctx context.Context, name string) (*models.Character, error) {
|
||||||
@@ -62,7 +42,7 @@ func (r *characterRepository) FindName(ctx context.Context, name string) (*model
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.character(row), nil
|
return r.character(psqlcore.SelectCharacterByIDRow(row)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *characterRepository) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
|
func (r *characterRepository) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
|
||||||
@@ -88,7 +68,7 @@ func (r *characterRepository) List(ctx context.Context, filter models.CharacterF
|
|||||||
}
|
}
|
||||||
if filter.Search != nil {
|
if filter.Search != nil {
|
||||||
params.FilterSearch = true
|
params.FilterSearch = true
|
||||||
params.Search = *filter.Search
|
params.Search = TSQueryFromSearch(*filter.Search)
|
||||||
}
|
}
|
||||||
if filter.Limit > 0 {
|
if filter.Limit > 0 {
|
||||||
params.LimitSize = int32(filter.Limit)
|
params.LimitSize = int32(filter.Limit)
|
||||||
@@ -153,7 +133,12 @@ func (r *characterRepository) Insert(ctx context.Context, character models.Chara
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &character, tx.Commit()
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &character, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *characterRepository) Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error) {
|
func (r *characterRepository) Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error) {
|
||||||
@@ -199,11 +184,17 @@ func (r *characterRepository) AddNick(ctx context.Context, character models.Char
|
|||||||
}
|
}
|
||||||
|
|
||||||
character.Nicks = append(character.Nicks, nick)
|
character.Nicks = append(character.Nicks, nick)
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &character, nil
|
return &character, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *characterRepository) RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
|
func (r *characterRepository) RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
|
||||||
err := psqlcore.New(r.db).AddCharacterNick(ctx, psqlcore.AddCharacterNickParams{ID: character.ID, Nick: nick})
|
err := psqlcore.New(r.db).RemoveCharacterNick(ctx, psqlcore.RemoveCharacterNickParams{ID: character.ID, Nick: nick})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -221,3 +212,23 @@ func (r *characterRepository) RemoveNick(ctx context.Context, character models.C
|
|||||||
func (r *characterRepository) Delete(ctx context.Context, character models.Character) error {
|
func (r *characterRepository) Delete(ctx context.Context, character models.Character) error {
|
||||||
return psqlcore.New(r.db).DeleteCharacter(ctx, character.ID)
|
return psqlcore.New(r.db).DeleteCharacter(ctx, character.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *characterRepository) character(row psqlcore.SelectCharacterByIDRow) *models.Character {
|
||||||
|
return &models.Character{
|
||||||
|
ID: strings.Trim(row.ID, " "),
|
||||||
|
Nicks: row.Nicks,
|
||||||
|
Name: row.Name,
|
||||||
|
ShortName: row.ShortName,
|
||||||
|
Author: row.Author,
|
||||||
|
Description: row.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *characterRepository) characters(rows []psqlcore.SelectCharactersRow) []*models.Character {
|
||||||
|
results := make([]*models.Character, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
results = append(results, r.character(psqlcore.SelectCharacterByIDRow(row)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ func Connect(cfg config.Database) (*DB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.SetMaxIdleConns(32)
|
||||||
|
|
||||||
q := psqlcore.New(db)
|
q := psqlcore.New(db)
|
||||||
if err := q.EnsureCounter(timeout, "data_character_id"); err != nil {
|
if err := q.EnsureCounter(timeout, "data_character_id"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+15
-12
@@ -32,23 +32,22 @@ func (r *logRepository) List(ctx context.Context, filter models.LogFilter) ([]*m
|
|||||||
}
|
}
|
||||||
|
|
||||||
if filter.Search != nil {
|
if filter.Search != nil {
|
||||||
ids, err := q.SelectLogIDsFromPostSearch(ctx, *filter.Search)
|
params.FilterSearch = true
|
||||||
if err != nil {
|
params.Search = TSQueryFromSearch(*filter.Search)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
params.FilterShortID = true
|
|
||||||
params.ShortIds = ids
|
|
||||||
}
|
}
|
||||||
if filter.Open != nil {
|
if filter.Open != nil {
|
||||||
params.FilterOpen = true
|
params.FilterOpen = true
|
||||||
params.Open = *filter.Open
|
params.Open = *filter.Open
|
||||||
}
|
}
|
||||||
if filter.Channels != nil {
|
if len(filter.Characters) > 0 {
|
||||||
|
params.FilterCharacterID = true
|
||||||
|
params.CharacterIds = filter.Characters
|
||||||
|
}
|
||||||
|
if len(filter.Channels) > 0 {
|
||||||
params.FilterChannelName = true
|
params.FilterChannelName = true
|
||||||
params.ChannelNames = filter.Channels
|
params.ChannelNames = filter.Channels
|
||||||
}
|
}
|
||||||
if filter.Events != nil {
|
if len(filter.Events) > 0 {
|
||||||
params.FilterEventName = true
|
params.FilterEventName = true
|
||||||
params.EventNames = filter.Events
|
params.EventNames = filter.Events
|
||||||
}
|
}
|
||||||
@@ -106,6 +105,10 @@ func (r *logRepository) Insert(ctx context.Context, log models.Log) (*models.Log
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if log.CharacterIDs == nil {
|
||||||
|
log.CharacterIDs = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
err = q.InsertLog(ctx, psqlcore.InsertLogParams{
|
err = q.InsertLog(ctx, psqlcore.InsertLogParams{
|
||||||
ID: log.ID,
|
ID: log.ID,
|
||||||
ShortID: log.ShortID,
|
ShortID: log.ShortID,
|
||||||
@@ -172,7 +175,7 @@ func (r *logRepository) Delete(ctx context.Context, log models.Log) error {
|
|||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *logRepository) log(log psqlcore.Log) *models.Log {
|
func (r *logRepository) log(log psqlcore.SelectLogRow) *models.Log {
|
||||||
return &models.Log{
|
return &models.Log{
|
||||||
ID: log.ID,
|
ID: log.ID,
|
||||||
ShortID: log.ShortID,
|
ShortID: log.ShortID,
|
||||||
@@ -186,10 +189,10 @@ func (r *logRepository) log(log psqlcore.Log) *models.Log {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *logRepository) logs(logs []psqlcore.Log) []*models.Log {
|
func (r *logRepository) logs(logs []psqlcore.SelectLogsRow) []*models.Log {
|
||||||
results := make([]*models.Log, 0, len(logs))
|
results := make([]*models.Log, 0, len(logs))
|
||||||
for _, log := range logs {
|
for _, log := range logs {
|
||||||
results = append(results, r.log(log))
|
results = append(results, r.log(psqlcore.SelectLogRow(log)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE log ADD COLUMN "ts_vector" TSVECTOR;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE log DROP COLUMN "ts_vector";
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE INDEX log_index_search ON log USING GIN (ts_vector);
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP INDEX IF EXISTS log_index_search;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -47,7 +47,7 @@ func (r *postRepository) List(ctx context.Context, filter models.PostFilter) ([]
|
|||||||
}
|
}
|
||||||
if filter.Search != nil {
|
if filter.Search != nil {
|
||||||
params.FilterSearch = true
|
params.FilterSearch = true
|
||||||
params.Search = *filter.Search
|
params.Search = TSQueryFromSearch(*filter.Search)
|
||||||
}
|
}
|
||||||
if filter.Limit > 0 {
|
if filter.Limit > 0 {
|
||||||
params.LimitSize = int32(filter.Limit)
|
params.LimitSize = int32(filter.Limit)
|
||||||
@@ -78,12 +78,18 @@ func (r *postRepository) Insert(ctx context.Context, post models.Post) (*models.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = psqlcore.New(r.db).GenerateLogTSVector(ctx, post.LogID)
|
||||||
|
|
||||||
post.Position = int(position)
|
post.Position = int(position)
|
||||||
|
|
||||||
return &post, nil
|
return &post, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *postRepository) InsertMany(ctx context.Context, posts ...*models.Post) ([]*models.Post, error) {
|
func (r *postRepository) InsertMany(ctx context.Context, posts ...*models.Post) ([]*models.Post, error) {
|
||||||
|
if len(posts) == 0 {
|
||||||
|
return []*models.Post{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
allowedLogID := ""
|
allowedLogID := ""
|
||||||
for _, post := range posts {
|
for _, post := range posts {
|
||||||
if allowedLogID == "" {
|
if allowedLogID == "" {
|
||||||
@@ -119,6 +125,11 @@ func (r *postRepository) InsertMany(ctx context.Context, posts ...*models.Post)
|
|||||||
post.Position = int(offset) + i
|
post.Position = int(offset) + i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = psqlcore.New(r.db).GenerateLogTSVector(ctx, posts[0].LogID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return posts, nil
|
return posts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +147,8 @@ func (r *postRepository) Update(ctx context.Context, post models.Post, update mo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = psqlcore.New(r.db).GenerateLogTSVector(ctx, post.LogID)
|
||||||
|
|
||||||
return &post, nil
|
return &post, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,12 +219,25 @@ func (r *postRepository) Move(ctx context.Context, post models.Post, position in
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
positions, err := q.SelectPositionsByLogShortID(ctx, post.LogID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prev := int32(0)
|
||||||
|
for _, pos := range positions {
|
||||||
|
if pos != prev+1 {
|
||||||
|
return nil, errors.New("post discontinuity detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = pos
|
||||||
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.posts(posts), nil
|
return r.posts2(posts), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *postRepository) Delete(ctx context.Context, post models.Post) error {
|
func (r *postRepository) Delete(ctx context.Context, post models.Post) error {
|
||||||
@@ -241,10 +267,12 @@ func (r *postRepository) Delete(ctx context.Context, post models.Post) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = q.GenerateLogTSVector(ctx, post.LogID)
|
||||||
|
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *postRepository) post(post psqlcore.LogPost) *models.Post {
|
func (r *postRepository) post(post psqlcore.SelectPostRow) *models.Post {
|
||||||
return &models.Post{
|
return &models.Post{
|
||||||
ID: post.ID,
|
ID: post.ID,
|
||||||
LogID: post.LogShortID,
|
LogID: post.LogShortID,
|
||||||
@@ -256,10 +284,19 @@ func (r *postRepository) post(post psqlcore.LogPost) *models.Post {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *postRepository) posts(posts []psqlcore.LogPost) []*models.Post {
|
func (r *postRepository) posts(posts []psqlcore.SelectPostsRow) []*models.Post {
|
||||||
results := make([]*models.Post, 0, len(posts))
|
results := make([]*models.Post, 0, len(posts))
|
||||||
for _, post := range posts {
|
for _, post := range posts {
|
||||||
results = append(results, r.post(post))
|
results = append(results, r.post(psqlcore.SelectPostRow(post)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *postRepository) posts2(posts []psqlcore.SelectPostsByPositionRangeRow) []*models.Post {
|
||||||
|
results := make([]*models.Post, 0, len(posts))
|
||||||
|
for _, post := range posts {
|
||||||
|
results = append(results, r.post(psqlcore.SelectPostRow(post)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ SELECT * FROM story_chapter WHERE id=$1::TEXT LIMIT 1;
|
|||||||
|
|
||||||
-- name: SelectChapters :many
|
-- name: SelectChapters :many
|
||||||
SELECT * FROM story_chapter
|
SELECT * FROM story_chapter
|
||||||
WHERE (sqlx.arg(story_id)::TEXT == '' OR story_id = @story_id::TEXT)
|
WHERE (@story_id::TEXT = '' OR story_id = @story_id::TEXT)
|
||||||
ORDER BY created_date
|
ORDER BY created_date
|
||||||
LIMIT NULLIF(@limit_size::INT, 0);
|
LIMIT NULLIF(@limit_size::INT, 0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
-- name: SelectCharacterByID :one
|
-- name: SelectCharacterByID :one
|
||||||
SELECT * FROM data_character WHERE id = @id::text;
|
SELECT id, nicks, name, short_name, author, description FROM data_character WHERE id = @id::text;
|
||||||
|
|
||||||
-- name: SelectCharacterByNick :one
|
-- name: SelectCharacterByNick :one
|
||||||
SELECT * FROM data_character WHERE nicks <@ ARRAY[@nick::text];
|
SELECT id, nicks, name, short_name, author, description FROM data_character WHERE nicks <@ ARRAY[@nick::text];
|
||||||
|
|
||||||
-- name: SelectCharacterByName :one
|
-- name: SelectCharacterByName :one
|
||||||
SELECT * FROM data_character WHERE name = @name::text;
|
SELECT id, nicks, name, short_name, author, description FROM data_character WHERE name = @name::text;
|
||||||
|
|
||||||
-- name: SelectCharacters :many
|
-- name: SelectCharacters :many
|
||||||
SELECT * FROM data_character
|
SELECT id, nicks, name, short_name, author, description FROM data_character
|
||||||
WHERE (@filter_id::bool = false OR id = ANY(@ids::text[]))
|
WHERE (@filter_id::bool = false OR id = ANY(@ids::text[]))
|
||||||
AND (@filter_name::bool = false OR name = ANY(@names::text[]))
|
AND (@filter_name::bool = false OR name = ANY(@names::text[]))
|
||||||
AND (@filter_nick::bool = false OR nicks && (@nicks::text[]))
|
AND (@filter_nick::bool = false OR nicks && (@nicks::text[]))
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ INSERT INTO core_counter (id, value) VALUES (@id::text, 0) ON CONFLICT DO NOTHIN
|
|||||||
UPDATE core_counter SET value = value + 1 WHERE id = @id::text RETURNING value::int;
|
UPDATE core_counter SET value = value + 1 WHERE id = @id::text RETURNING value::int;
|
||||||
|
|
||||||
-- name: BumpCounter :exec
|
-- name: BumpCounter :exec
|
||||||
UPDATE core_counter SET value = value + 1 WHERE id = @id::text AND value <= @value::int;
|
UPDATE core_counter SET value = @value::int WHERE id = @id::text AND value <= @value::int;
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
-- name: SelectLog :one
|
-- name: SelectLog :one
|
||||||
SELECT * FROM log WHERE id=$1 OR short_id=$1 LIMIT 1;
|
SELECT id, short_id, character_ids, date, channel_name, event_name, title, description, open FROM log WHERE id=$1 OR short_id=$1 LIMIT 1;
|
||||||
|
|
||||||
-- name: SelectLogs :many
|
-- name: SelectLogs :many
|
||||||
SELECT * FROM log
|
SELECT id, short_id, character_ids, date, channel_name, event_name, title, description, open FROM log
|
||||||
WHERE (@filter_short_id::BOOL = false OR short_id = ANY(@short_ids::TEXT[]))
|
WHERE (@filter_short_id::BOOL = false OR short_id = ANY(@short_ids::TEXT[]))
|
||||||
AND (@filter_character_id::BOOL = false OR character_ids && (@character_ids::TEXT[]))
|
AND (@filter_character_id::BOOL = false OR character_ids @> (@character_ids::TEXT[]))
|
||||||
AND (@filter_channel_name::BOOL = false OR channel_name = ANY(@channel_names::TEXT[]))
|
AND (@filter_channel_name::BOOL = false OR channel_name = ANY(@channel_names::TEXT[]))
|
||||||
AND (@filter_event_name::BOOL = false OR event_name = ANY(@event_names::TEXT[]))
|
AND (@filter_event_name::BOOL = false OR event_name = ANY(@event_names::TEXT[]))
|
||||||
AND (@filter_open::BOOL = false OR open = @open::BOOL)
|
AND (@filter_open::BOOL = false OR open = @open::BOOL)
|
||||||
AND (@filter_earlist_date::BOOL = false OR date >= @earliest_date::TIMESTAMP)
|
AND (@filter_earlist_date::BOOL = false OR date >= @earliest_date::TIMESTAMP)
|
||||||
AND (@filter_lastest_date::BOOL = false OR date <= @latest_date::TIMESTAMP)
|
AND (@filter_lastest_date::BOOL = false OR date <= @latest_date::TIMESTAMP)
|
||||||
ORDER BY date
|
AND (@filter_search::BOOL = false OR "ts_vector" @@ to_tsquery(@search::TEXT))
|
||||||
|
ORDER BY date DESC
|
||||||
LIMIT NULLIF(@limit_size::INT, 0);
|
LIMIT NULLIF(@limit_size::INT, 0);
|
||||||
|
|
||||||
-- name: InsertLog :exec
|
-- name: InsertLog :exec
|
||||||
@@ -28,5 +29,16 @@ SET title = @title,
|
|||||||
character_ids = @character_ids
|
character_ids = @character_ids
|
||||||
WHERE id = @id;
|
WHERE id = @id;
|
||||||
|
|
||||||
|
-- name: GenerateLogTSVector :exec
|
||||||
|
UPDATE log
|
||||||
|
SET "ts_vector" = (
|
||||||
|
SELECT TO_TSVECTOR(
|
||||||
|
immutable_array_to_string(array_agg(text), '\n\n')
|
||||||
|
)
|
||||||
|
FROM log_post
|
||||||
|
WHERE log_short_id = @short_id
|
||||||
|
)
|
||||||
|
WHERE short_id = @short_id;
|
||||||
|
|
||||||
-- name: DeleteLog :exec
|
-- name: DeleteLog :exec
|
||||||
DELETE FROM log WHERE id=$1;
|
DELETE FROM log WHERE id=$1;
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
-- name: SelectPost :one
|
-- name: SelectPost :one
|
||||||
SELECT * FROM log_post WHERE id=$1 LIMIT 1;
|
SELECT id, log_short_id, time, kind, nick, text, position FROM log_post WHERE id=$1 LIMIT 1;
|
||||||
|
|
||||||
-- name: SelectLogIDsFromPostSearch :many
|
-- name: SelectLogIDsFromPostSearch :many
|
||||||
SELECT DISTINCT(log_short_id) FROM log_post WHERE "ts_vector" @@ to_tsquery(@search::TEXT);
|
SELECT DISTINCT(log_short_id) FROM log_post WHERE "ts_vector" @@ to_tsquery(@search::TEXT);
|
||||||
|
|
||||||
-- name: SelectPostsByPositionRange :many
|
-- name: SelectPostsByPositionRange :many
|
||||||
SELECT * FROM log_post WHERE log_short_id = @log_short_id AND position >= @from_position AND position = @to_position;
|
SELECT id, log_short_id, time, kind, nick, text, position FROM log_post WHERE log_short_id = @log_short_id AND position >= @from_position AND position <= @to_position;
|
||||||
|
|
||||||
|
-- name: SelectPositionsByLogShortID :many
|
||||||
|
SELECT position FROM log_post WHERE log_short_id = $1 ORDER BY position;
|
||||||
|
|
||||||
-- name: SelectPosts :many
|
-- name: SelectPosts :many
|
||||||
SELECT * FROM log_post
|
SELECT id, log_short_id, time, kind, nick, text, position FROM log_post
|
||||||
WHERE (@filter_ids::BOOL = false OR id = ANY(@ids::TEXT[]))
|
WHERE (@filter_ids::BOOL = false OR id = ANY(@ids::TEXT[]))
|
||||||
AND (@filter_kinds::BOOL = false OR kind = ANY(@kinds::TEXT[]))
|
AND (@filter_kinds::BOOL = false OR kind = ANY(@kinds::TEXT[]))
|
||||||
AND (@filter_log_short_id::BOOL = false OR log_short_id = @log_short_id)
|
AND (@filter_log_short_id::BOOL = false OR log_short_id = @log_short_id)
|
||||||
@@ -20,13 +23,13 @@ LIMIT NULLIF(@limit_size::INT, 0);
|
|||||||
INSERT INTO log_post (id, log_short_id, time, kind, nick, text, position, ts_vector)
|
INSERT INTO log_post (id, log_short_id, time, kind, nick, text, position, ts_vector)
|
||||||
SELECT @id, @log_short_id, @time, @kind, @nick, @text, COALESCE(MAX(position), 0)+1, to_tsvector(@nick || ' ' || @text)
|
SELECT @id, @log_short_id, @time, @kind, @nick, @text, COALESCE(MAX(position), 0)+1, to_tsvector(@nick || ' ' || @text)
|
||||||
FROM log_post
|
FROM log_post
|
||||||
WHERE log_short_id=@log_short_id
|
WHERE log_short_id = @log_short_id
|
||||||
RETURNING position;
|
RETURNING position;
|
||||||
|
|
||||||
-- name: InsertPosts :one
|
-- name: InsertPosts :one
|
||||||
INSERT INTO log_post (id, log_short_id, time, kind, nick, text, position, ts_vector)
|
INSERT INTO log_post (id, log_short_id, time, kind, nick, text, position, ts_vector)
|
||||||
SELECT UNNEST(@ids::TEXT[]), @log_short_id, UNNEST(@times::TIMESTAMP[]), UNNEST(@kinds::TEXT[]),
|
SELECT UNNEST(@ids::TEXT[]), @log_short_id, UNNEST(@times::TIMESTAMP[]), UNNEST(@kinds::TEXT[]),
|
||||||
UNNEST(@nicks::TEXT[]), UNNEST(@texts::TEXT[]), COALESCE(MAX(position), 1) + UNNEST(@offsets::INT[]),
|
UNNEST(@nicks::TEXT[]), UNNEST(@texts::TEXT[]), COALESCE(MAX(position), 0) + UNNEST(@offsets::INT[]),
|
||||||
to_tsvector(UNNEST(@nicks::TEXT[]) || ' ' || UNNEST(@texts::TEXT[]))
|
to_tsvector(UNNEST(@nicks::TEXT[]) || ' ' || UNNEST(@texts::TEXT[]))
|
||||||
FROM log_post
|
FROM log_post
|
||||||
WHERE log_short_id = @log_short_id
|
WHERE log_short_id = @log_short_id
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ SELECT * FROM story WHERE id = $1 LIMIT 1;
|
|||||||
-- name: SelectStories :many
|
-- name: SelectStories :many
|
||||||
SELECT * FROM story
|
SELECT * FROM story
|
||||||
WHERE (@filter_id::bool = false OR id = ANY(@ids::text[]))
|
WHERE (@filter_id::bool = false OR id = ANY(@ids::text[]))
|
||||||
AND (@filter_author::bool = false OR name = @author::text)
|
AND (@filter_author::bool = false OR author = @author::text)
|
||||||
AND (@filter_earlist_fictional_date::bool = false OR fictional_date >= @earliest_fictional_date::timestamp)
|
AND (@filter_earlist_fictional_date::bool = false OR fictional_date >= @earliest_fictional_date::timestamp)
|
||||||
AND (@filter_lastest_fictional_date::bool = false OR fictional_date <= @latest_fictional_date::timestamp)
|
AND (@filter_lastest_fictional_date::bool = false OR fictional_date <= @latest_fictional_date::timestamp)
|
||||||
AND (@filter_category::bool = false OR category = @category::text)
|
AND (@filter_category::bool = false OR category = @category::text)
|
||||||
AND (@filter_open::bool = false OR open = @open::bool)
|
AND (@filter_open::bool = false OR open = @open::bool)
|
||||||
AND (@filter_unlisted::bool = false OR unlisted = @unlisted::bool)
|
AND (@filter_listed::bool = false OR listed = @listed::bool)
|
||||||
ORDER BY updated_date
|
ORDER BY updated_date DESC
|
||||||
LIMIT NULLIF(@limit_size::INT, 0);
|
LIMIT NULLIF(@limit_size::INT, 0);
|
||||||
|
|
||||||
-- name: InsertStory :exec
|
-- name: InsertStory :exec
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ SELECT * FROM common_tag WHERE tag = @tag_name::text ORDER BY tag;
|
|||||||
SELECT * FROM common_tag WHERE tag = ANY(@tag_names::text[]) ORDER BY tag;
|
SELECT * FROM common_tag WHERE tag = ANY(@tag_names::text[]) ORDER BY tag;
|
||||||
|
|
||||||
-- name: SelectTargetsByTags :many
|
-- name: SelectTargetsByTags :many
|
||||||
SELECT DISTINCT(target_id) FROM common_tag WHERE tag = ANY(@tag_names::text[]) AND target_kind = @target_kind::text ORDER BY tag;
|
SELECT DISTINCT target_id FROM common_tag WHERE tag = ANY(@tag_names::text[]) AND target_kind = @target_kind::text;
|
||||||
|
|
||||||
-- name: SelectTagsByTarget :many
|
-- name: SelectTagsByTarget :many
|
||||||
SELECT * FROM common_tag WHERE target_kind = @target_kind AND target_id = @target_id::text ORDER BY tag;
|
SELECT * FROM common_tag WHERE target_kind = @target_kind AND target_id = @target_id::text ORDER BY tag;
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
params.FilterID = true
|
||||||
|
params.Ids = targets
|
||||||
|
|
||||||
if len(params.Ids) == 0 {
|
if len(params.Ids) == 0 {
|
||||||
return []*models.Story{}, nil
|
return []*models.Story{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
params.FilterID = true
|
|
||||||
params.Ids = targets
|
|
||||||
}
|
}
|
||||||
if filter.Author != nil {
|
if filter.Author != nil {
|
||||||
params.FilterAuthor = true
|
params.FilterAuthor = true
|
||||||
@@ -72,8 +72,8 @@ func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) (
|
|||||||
params.Open = *filter.Open
|
params.Open = *filter.Open
|
||||||
}
|
}
|
||||||
if filter.Unlisted != nil {
|
if filter.Unlisted != nil {
|
||||||
params.FilterUnlisted = true
|
params.FilterListed = true
|
||||||
params.Unlisted = *filter.Unlisted
|
params.Listed = !*filter.Unlisted
|
||||||
}
|
}
|
||||||
if filter.Limit > 0 {
|
if filter.Limit > 0 {
|
||||||
params.LimitSize = int32(filter.Limit)
|
params.LimitSize = int32(filter.Limit)
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// TSQueryFromSearch generates a TS query from a typical search string.
|
||||||
|
func TSQueryFromSearch(search string) string {
|
||||||
|
if strings.HasPrefix(search, "tsq:") {
|
||||||
|
return search[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Split(search, " ")
|
||||||
|
inQuotes := false
|
||||||
|
result := ""
|
||||||
|
|
||||||
|
for i, token := range tokens {
|
||||||
|
clearQuotes := false
|
||||||
|
startQuotes := false
|
||||||
|
|
||||||
|
if strings.HasPrefix(token, "\"") {
|
||||||
|
token = token[1:]
|
||||||
|
startQuotes = true
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(token, "\"") {
|
||||||
|
token = token[:len(token)-1]
|
||||||
|
clearQuotes = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if startQuotes {
|
||||||
|
if i > 0 {
|
||||||
|
result += " & "
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "("
|
||||||
|
} else if inQuotes {
|
||||||
|
result += "<->"
|
||||||
|
} else if i != 0 {
|
||||||
|
result += " & "
|
||||||
|
}
|
||||||
|
|
||||||
|
result += token
|
||||||
|
|
||||||
|
if startQuotes {
|
||||||
|
inQuotes = true
|
||||||
|
}
|
||||||
|
if clearQuotes {
|
||||||
|
inQuotes = false
|
||||||
|
result += ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inQuotes {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimLeft(result, " ")
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTSQueryFromSearch(t *testing.T) {
|
||||||
|
rows := [][2]string{
|
||||||
|
{`asari matron`, `asari & matron`},
|
||||||
|
{`"asari matron"`, `(asari<->matron)`},
|
||||||
|
{`"asari matron" blue`, `(asari<->matron) & blue`},
|
||||||
|
{`"christmas present" "wrapping paper"`, `(christmas<->present) & (wrapping<->paper)`},
|
||||||
|
{`stuff`, `stuff`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, row := range rows {
|
||||||
|
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
|
||||||
|
assert.Equal(t, row[1], TSQueryFromSearch(row[0]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -20,7 +20,7 @@ func (tag *Tag) Decode(v string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
kind := TagKind("")
|
kind := TagKind("")
|
||||||
err := kind.UnmarshalGQL(v)
|
err := kind.UnmarshalGQL(split[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-2
@@ -74,9 +74,23 @@ func (s *CharacterService) List(ctx context.Context, filter models.CharacterFilt
|
|||||||
return characters, nil
|
return characters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.refreshLogs()
|
characters, err := s.characters.List(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return s.characters.List(ctx, filter)
|
sort.Slice(characters, func(i, j int) bool {
|
||||||
|
if len(characters[i].ID) > len(characters[j].ID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(characters[i].ID) < len(characters[j].ID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Compare(characters[i].ID, characters[j].ID) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return characters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, author, description string) (*models.Character, error) {
|
func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, author, description string) (*models.Character, error) {
|
||||||
|
|||||||
+9
-1
@@ -718,11 +718,19 @@ func (s *LogService) RefreshAllLogCharacters(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
s.unknownNicksMutex.Unlock()
|
s.unknownNicksMutex.Unlock()
|
||||||
|
|
||||||
|
tokens := make(chan struct{}, 33)
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
tokens <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
eg := errgroup.Group{}
|
eg := errgroup.Group{}
|
||||||
for i := range logs {
|
for i := range logs {
|
||||||
l := logs[i]
|
l := logs[i]
|
||||||
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
|
<-tokens
|
||||||
|
defer func() { tokens <- struct{}{} }()
|
||||||
|
|
||||||
_, err := s.refreshLogCharacters(ctx, *l, characterMap, true)
|
_, err := s.refreshLogCharacters(ctx, *l, characterMap, true)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -748,7 +756,7 @@ func (s *LogService) RefreshLogCharacters(ctx context.Context, log models.Log) (
|
|||||||
func (s *LogService) refreshLogCharacters(ctx context.Context, log models.Log, characterMap map[string]*models.Character, useUnknownNicks bool) (*models.Log, error) {
|
func (s *LogService) refreshLogCharacters(ctx context.Context, log models.Log, characterMap map[string]*models.Character, useUnknownNicks bool) (*models.Log, error) {
|
||||||
posts, err := s.ListPosts(ctx, &models.PostFilter{LogID: &log.ShortID})
|
posts, err := s.ListPosts(ctx, &models.PostFilter{LogID: &log.ShortID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &log, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
counts := make(map[string]int)
|
counts := make(map[string]int)
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ func NewBundle(db database.Database, spaceClient *space.Client) *Bundle {
|
|||||||
changes: db.Changes(),
|
changes: db.Changes(),
|
||||||
authService: bundle.Auth,
|
authService: bundle.Auth,
|
||||||
}
|
}
|
||||||
bundle.Files = &FileService{
|
if spaceClient != nil {
|
||||||
files: db.Files(),
|
bundle.Files = &FileService{
|
||||||
authService: bundle.Auth,
|
files: db.Files(),
|
||||||
changeService: bundle.Changes,
|
authService: bundle.Auth,
|
||||||
space: spaceClient,
|
changeService: bundle.Changes,
|
||||||
|
space: spaceClient,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bundle.Tags = &TagService{tags: db.Tags()}
|
bundle.Tags = &TagService{tags: db.Tags()}
|
||||||
bundle.Characters = &CharacterService{
|
bundle.Characters = &CharacterService{
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ func (s *StoryService) ListStories(ctx context.Context, filter models.StoryFilte
|
|||||||
return nil, errors.New("you cannot view your own unlisted stories")
|
return nil, errors.New("you cannot view your own unlisted stories")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unlistedValue := false
|
||||||
|
filter.Unlisted = &unlistedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.stories.List(ctx, filter)
|
return s.stories.List(ctx, filter)
|
||||||
|
|||||||
Reference in New Issue
Block a user