Basis Suche Inhalte

This commit is contained in:
Simon Martens
2025-02-28 22:31:19 +01:00
parent 4581f34dd9
commit ca33ec7be3
20 changed files with 1290 additions and 594 deletions

View File

@@ -1,11 +1,9 @@
package pages
import (
"database/sql"
"fmt"
"net/http"
"slices"
"strings"
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
@@ -23,9 +21,9 @@ const (
URL_SUCHE = "/suche/{type}"
URL_SUCHE_ALT = "/suche/{$}"
DEFAULT_SUCHE = "/suche/baende"
PARAM_QUERY = "q"
PARAM_EXTENDED = "extended"
TEMPLATE_SUCHE = "/suche/"
PARAM_QUERY = "q"
)
var availableTypes = []string{"baende", "beitraege"}
@@ -60,6 +58,10 @@ func (p *SuchePage) Setup(router *router.Router[*core.RequestEvent], app core.Ap
return p.SearchBaendeRequest(app, engine, e, *allparas)
}
if allparas.IsBeitraegeSearch() {
return p.SearchBeitraegeRequest(app, engine, e, *allparas)
}
data := make(map[string]interface{})
data["parameters"] = allparas
return engine.Response200(e, p.Template+paras.Collection+"/", data, p.Layout)
@@ -72,10 +74,10 @@ func (p *SuchePage) SimpleSearchReihenRequest(app core.App, engine *templating.E
return engine.Response404(e, nil, nil)
}
func (p *SuchePage) SearchBaendeRequest(app core.App, engine *templating.Engine, e *core.RequestEvent, params SearchParameters) error {
func (p *SuchePage) SearchBeitraegeRequest(app core.App, engine *templating.Engine, e *core.RequestEvent, params SearchParameters) error {
data := make(map[string]interface{})
result, err := SimpleSearchBaende(app, params)
result, err := NewSearchBeitraege(app, params)
if err != nil {
return engine.Response404(e, err, nil)
}
@@ -85,38 +87,20 @@ func (p *SuchePage) SearchBaendeRequest(app core.App, engine *templating.Engine,
return engine.Response200(e, p.Template+params.Collection+"/", data, p.Layout)
}
const (
BEITRAEGE_PARAM_ALM_NR = "nr"
BEITRAEGE_PARAM_TITLE = "title"
BEITRAEGE_PARAM_INCIPT = "incipit"
BEITRAEGE_PARAM_PERSONS = "persons"
BEITRAEGE_PARAM_ANNOTATIONS = "annotations"
// INFO: this is expanded search only:
BEITRAEGE_PARAM_PSEUDONYMS = "pseudonyms"
// INFO: these are filter types & expanded search:
BEITRAEGE_PARAM_TYPE = "type"
BEITRAEGE_PARAM_SCANS = "scans"
func (p *SuchePage) SearchBaendeRequest(app core.App, engine *templating.Engine, e *core.RequestEvent, params SearchParameters) error {
data := make(map[string]interface{})
REIHEN_PARAM_TITLE = "title"
REIHEN_PARAM_ANNOTATIONS = "annotations"
REIHEN_PARAM_REFERENCES = "references"
result, err := NewSearchBaende(app, params)
if err != nil {
return engine.Response404(e, err, nil)
}
BAENDE_PARAM_ALM_NR = "alm"
BAENDE_PARAM_TITLE = "title"
BAENDE_PARAM_SERIES = "series"
BAENDE_PARAM_PERSONS = "persons"
BAENDE_PARAM_PLACES = "places"
BAENDE_PARAM_REFS = "references"
BAENDE_PARAM_ANNOTATIONS = "annotations"
BAENDE_PARAM_YEAR = "year"
// INFO: this is expanded search only:
BAENDE_PARAM_PSEUDONYMS = "pseudonyms"
// INFO: this is a filter type & expanded search:
BAENDE_PARAM_STATE = "state" // STATE: "full" "partial" "none"
)
data["parameters"] = params
data["result"] = result
return engine.Response200(e, p.Template+params.Collection+"/", data, p.Layout)
}
var ErrInvalidCollectionType = fmt.Errorf("Invalid collection type")
var ErrNoQuery = fmt.Errorf("No query")
type Parameters struct {
Extended bool
@@ -145,437 +129,3 @@ func NewParameters(e *core.RequestEvent) (*Parameters, error) {
func (p *Parameters) NormalizeQuery() dbmodels.Query {
return dbmodels.NormalizeQuery(p.Query)
}
type SearchParameters struct {
Parameters
Sort string
Annotations bool
Persons bool
Title bool
Series bool
Places bool
Refs bool
Year bool
AnnotationsString string
PersonsString string
TitleString string
AlmString string
SeriesString string
PlacesString string
RefsString string
YearString string
TypeFilter string
}
func NewSearchParameters(e *core.RequestEvent, p Parameters) (*SearchParameters, error) {
title := e.Request.URL.Query().Get(BAENDE_PARAM_TITLE) == "on"
series := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES) == "on"
persons := e.Request.URL.Query().Get(BAENDE_PARAM_PERSONS) == "on"
places := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES) == "on"
refs := e.Request.URL.Query().Get(BAENDE_PARAM_REFS) == "on"
annotations := e.Request.URL.Query().Get(BAENDE_PARAM_ANNOTATIONS) == "on"
year := e.Request.URL.Query().Get(BAENDE_PARAM_YEAR) == "on"
almstring := e.Request.URL.Query().Get(BAENDE_PARAM_ALM_NR + "string")
titlestring := e.Request.URL.Query().Get(BAENDE_PARAM_TITLE + "string")
seriesstring := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES + "string")
personsstring := e.Request.URL.Query().Get(BAENDE_PARAM_PERSONS + "string")
placesstring := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES + "string")
annotationsstring := e.Request.URL.Query().Get(BAENDE_PARAM_ANNOTATIONS + "string")
yearstring := e.Request.URL.Query().Get(BAENDE_PARAM_YEAR + "string")
refss := e.Request.URL.Query().Get(BAENDE_PARAM_REFS + "string")
if refss != "" {
refss = "\"" + refss + "\""
}
sort := e.Request.URL.Query().Get("sort")
if sort == "" {
sort = "year"
}
return &SearchParameters{
Parameters: p,
Sort: sort,
// INFO: Common parameters
Title: title,
Persons: persons,
Annotations: annotations,
// INFO: Baende parameters
Places: places,
Refs: refs,
Year: year,
Series: series,
// INFO: Expanded search
AlmString: almstring,
TitleString: titlestring,
SeriesString: seriesstring,
PersonsString: personsstring,
PlacesString: placesstring,
RefsString: refss,
AnnotationsString: annotationsstring,
YearString: yearstring,
}, nil
}
func (p SearchParameters) AllSearchTerms() string {
res := []string{}
res = append(res, p.includedParams(p.Query)...)
res = append(res, p.includedParams(p.AnnotationsString)...)
res = append(res, p.includedParams(p.PersonsString)...)
res = append(res, p.includedParams(p.TitleString)...)
res = append(res, p.includedParams(p.SeriesString)...)
res = append(res, p.includedParams(p.PlacesString)...)
res = append(res, p.includedParams(p.RefsString)...)
res = append(res, p.includedParams(p.YearString)...)
res = append(res, p.AlmString)
return strings.Join(res, " ")
}
func (p SearchParameters) includedParams(q string) []string {
res := []string{}
que := dbmodels.NormalizeQuery(q)
for _, qq := range que.Include {
res = append(res, qq)
}
for _, qq := range que.UnsafeI {
res = append(res, qq)
}
return res
}
func (p SearchParameters) ToQueryParams() string {
q := "?"
// TODO: use variables, url escape
if p.Extended {
q += "extended=true"
}
if p.Query != "" {
q += fmt.Sprintf("q=%s", p.Query)
if p.Title {
q += "&title=on"
}
if p.Persons {
q += "&persons=on"
}
if p.Annotations {
q += "&annotations=on"
}
if p.Series {
q += "&series=on"
}
if p.Places {
q += "&places=on"
}
if p.Refs {
q += "&references=on"
}
if p.Year {
q += "&year=on"
}
}
if p.YearString != "" {
q += fmt.Sprintf("&yearstring=%s", p.YearString)
}
if p.AnnotationsString != "" {
q += fmt.Sprintf("&annotationsstring=%s", p.AnnotationsString)
}
if p.PersonsString != "" {
q += fmt.Sprintf("&personsstring=%s", p.PersonsString)
}
if p.TitleString != "" {
q += fmt.Sprintf("&titlestring=%s", p.TitleString)
}
if p.SeriesString != "" {
q += fmt.Sprintf("&seriesstring=%s", p.SeriesString)
}
if p.PlacesString != "" {
q += fmt.Sprintf("&placesstring=%s", p.PlacesString)
}
if p.RefsString != "" {
q += fmt.Sprintf("&refsstring=%s", p.RefsString)
}
return q
}
func (p SearchParameters) IsBaendeSearch() bool {
return p.Collection == "baende" && (p.Query != "" || p.AlmString != "" || p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != "")
}
func (p SearchParameters) FieldSetBaende() []dbmodels.FTS5QueryRequest {
ret := []dbmodels.FTS5QueryRequest{}
if p.Query != "" {
fields := []string{dbmodels.ID_FIELD}
if p.Title {
// INFO: Preferred Title is not here to avoid hitting the Reihentitel
fields = append(fields,
dbmodels.TITLE_STMT_FIELD,
dbmodels.SUBTITLE_STMT_FIELD,
dbmodels.INCIPIT_STMT_FIELD,
dbmodels.VARIANT_TITLE_FIELD,
dbmodels.PARALLEL_TITLE_FIELD,
)
}
if p.Series {
fields = append(fields, dbmodels.SERIES_TABLE)
}
if p.Persons {
fields = append(fields, dbmodels.RESPONSIBILITY_STMT_FIELD, dbmodels.AGENTS_TABLE)
}
if p.Places {
fields = append(fields, dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD)
}
if p.Refs {
fields = append(fields, dbmodels.REFERENCES_FIELD)
}
if p.Annotations {
fields = append(fields, dbmodels.ANNOTATION_FIELD)
}
if p.Year {
fields = append(fields, dbmodels.YEAR_FIELD)
}
que := p.NormalizeQuery()
req := dbmodels.IntoQueryRequests(fields, que)
ret = append(ret, req...)
}
if p.AnnotationsString != "" {
que := dbmodels.NormalizeQuery(p.AnnotationsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.ANNOTATION_FIELD}, que)
ret = append(ret, req...)
}
if p.PersonsString != "" {
que := dbmodels.NormalizeQuery(p.PersonsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.AGENTS_TABLE, dbmodels.RESPONSIBILITY_STMT_FIELD}, que)
ret = append(ret, req...)
}
if p.TitleString != "" {
que := dbmodels.NormalizeQuery(p.TitleString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.INCIPIT_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD}, que)
ret = append(ret, req...)
}
if p.SeriesString != "" {
que := dbmodels.NormalizeQuery(p.SeriesString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.SERIES_TABLE}, que)
ret = append(ret, req...)
}
if p.PlacesString != "" {
que := dbmodels.NormalizeQuery(p.PlacesString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD}, que)
ret = append(ret, req...)
}
if p.RefsString != "" {
que := dbmodels.NormalizeQuery(p.RefsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.REFERENCES_FIELD}, que)
ret = append(ret, req...)
}
if p.YearString != "" {
que := dbmodels.NormalizeQuery(p.YearString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.YEAR_FIELD}, que)
ret = append(ret, req...)
}
return ret
}
func (p SearchParameters) IsExtendedSearch() bool {
return p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.AlmString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != ""
}
func (p SearchParameters) NormalizeQuery() dbmodels.Query {
return dbmodels.NormalizeQuery(p.Query)
}
type SearchResultBaende struct {
Queries []dbmodels.FTS5QueryRequest
// these are the sorted IDs for hits
Hits []string
Series map[string]*dbmodels.Series // <- Key: Series ID
Entries map[string]*dbmodels.Entry // <- Key: Entry ID
Places map[string]*dbmodels.Place // <- All places, Key: Place IDs
Agents map[string]*dbmodels.Agent // <- Key: Agent IDs
// INFO: this is as they say doppelt gemoppelt bc of a logic error i made while tired
EntriesSeries map[string][]*dbmodels.REntriesSeries // <- Key: Entry ID
SeriesEntries map[string][]*dbmodels.REntriesSeries // <- Key: Series ID
EntriesAgents map[string][]*dbmodels.REntriesAgents // <- Key: Entry ID
}
func EmptyResultBaende() *SearchResultBaende {
return &SearchResultBaende{
Hits: []string{},
Series: make(map[string]*dbmodels.Series),
Entries: make(map[string]*dbmodels.Entry),
Places: make(map[string]*dbmodels.Place),
Agents: make(map[string]*dbmodels.Agent),
EntriesSeries: make(map[string][]*dbmodels.REntriesSeries),
SeriesEntries: make(map[string][]*dbmodels.REntriesSeries),
EntriesAgents: make(map[string][]*dbmodels.REntriesAgents),
}
}
func SimpleSearchBaende(app core.App, params SearchParameters) (*SearchResultBaende, error) {
entries := []*dbmodels.Entry{}
queries := params.FieldSetBaende()
if params.AlmString != "" {
e, err := dbmodels.Entries_MusenalmID(app, params.AlmString)
if err != nil && err == sql.ErrNoRows {
return EmptyResultBaende(), nil
} else if err != nil {
return nil, err
}
entries = append(entries, e)
} else {
if len(queries) == 0 {
return nil, ErrNoQuery
}
ids, err := dbmodels.FTS5Search(app, dbmodels.ENTRIES_TABLE, queries...)
if err != nil {
return nil, err
}
resultids := []any{}
for _, id := range ids {
resultids = append(resultids, id.ID)
}
e, err := dbmodels.Entries_IDs(app, resultids)
if err != nil {
return nil, err
}
entries = e
}
resultids := []any{}
for _, entry := range entries {
resultids = append(resultids, entry.Id)
}
entriesmap := make(map[string]*dbmodels.Entry)
for _, entry := range entries {
entriesmap[entry.Id] = entry
}
series, relations, err := Series_Entries(app, entries)
if err != nil {
return nil, err
}
seriesmap := make(map[string]*dbmodels.Series)
for _, s := range series {
seriesmap[s.Id] = s
}
relationsmap := make(map[string][]*dbmodels.REntriesSeries)
invrelationsmap := make(map[string][]*dbmodels.REntriesSeries)
for _, r := range relations {
invrelationsmap[r.Series()] = append(invrelationsmap[r.Series()], r)
relationsmap[r.Entry()] = append(relationsmap[r.Entry()], r)
}
agents, arelations, err := Agents_Entries_IDs(app, resultids)
if err != nil {
return nil, err
}
agentsmap := make(map[string]*dbmodels.Agent)
for _, a := range agents {
agentsmap[a.Id] = a
}
relationsagentsmap := make(map[string][]*dbmodels.REntriesAgents)
for _, r := range arelations {
relationsagentsmap[r.Entry()] = append(relationsagentsmap[r.Entry()], r)
}
placesids := []any{}
for _, entry := range entries {
for _, place := range entry.Places() {
placesids = append(placesids, place)
}
}
places, err := dbmodels.Places_IDs(app, placesids)
if err != nil {
return nil, err
}
placesmap := make(map[string]*dbmodels.Place)
for _, place := range places {
placesmap[place.Id] = place
}
hits := []string{}
if params.Sort == "series" {
dbmodels.Sort_Series_Title(series)
for _, s := range series {
hits = append(hits, s.Id)
}
} else {
dbmodels.Sort_Entries_Year_Title(entries)
for _, e := range entries {
hits = append(hits, e.Id)
}
}
return &SearchResultBaende{
Hits: hits,
Series: seriesmap,
Entries: entriesmap,
Places: placesmap,
Agents: agentsmap,
EntriesSeries: relationsmap,
SeriesEntries: invrelationsmap,
EntriesAgents: relationsagentsmap,
}, nil
}
func (r SearchResultBaende) Count() int {
return len(r.Entries)
}
func (r SearchResultBaende) SeriesCount() int {
return len(r.Series)
}
func Agents_Entries_IDs(app core.App, ids []any) ([]*dbmodels.Agent, []*dbmodels.REntriesAgents, error) {
relations, err := dbmodels.REntriesAgents_Entries(app, ids)
if err != nil {
return nil, nil, err
}
agentids := []any{}
for _, r := range relations {
agentids = append(agentids, r.Agent())
}
agents, err := dbmodels.Agents_IDs(app, agentids)
if err != nil {
return nil, nil, err
}
return agents, relations, nil
}

186
pages/suche_baende.go Normal file
View File

@@ -0,0 +1,186 @@
package pages
import (
"database/sql"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/pocketbase/pocketbase/core"
)
type SearchResultBaende struct {
Queries []dbmodels.FTS5QueryRequest
// these are the sorted IDs for hits
Hits []string
Series map[string]*dbmodels.Series // <- Key: Series ID
Entries map[string]*dbmodels.Entry // <- Key: Entry ID
Places map[string]*dbmodels.Place // <- All places, Key: Place IDs
Agents map[string]*dbmodels.Agent // <- Key: Agent IDs
// INFO: this is as they say doppelt gemoppelt bc of a logic error i made while tired
EntriesSeries map[string][]*dbmodels.REntriesSeries // <- Key: Entry ID
SeriesEntries map[string][]*dbmodels.REntriesSeries // <- Key: Series ID
EntriesAgents map[string][]*dbmodels.REntriesAgents // <- Key: Entry ID
}
func EmptyResultBaende() *SearchResultBaende {
return &SearchResultBaende{
Hits: []string{},
Series: make(map[string]*dbmodels.Series),
Entries: make(map[string]*dbmodels.Entry),
Places: make(map[string]*dbmodels.Place),
Agents: make(map[string]*dbmodels.Agent),
EntriesSeries: make(map[string][]*dbmodels.REntriesSeries),
SeriesEntries: make(map[string][]*dbmodels.REntriesSeries),
EntriesAgents: make(map[string][]*dbmodels.REntriesAgents),
}
}
func NewSearchBaende(app core.App, params SearchParameters) (*SearchResultBaende, error) {
entries := []*dbmodels.Entry{}
queries := params.FieldSetBaende()
if params.AlmString != "" {
e, err := dbmodels.Entries_MusenalmID(app, params.AlmString)
if err != nil && err == sql.ErrNoRows {
return EmptyResultBaende(), nil
} else if err != nil {
return nil, err
}
entries = append(entries, e)
} else {
if len(queries) == 0 {
return nil, ErrNoQuery
}
ids, err := dbmodels.FTS5Search(app, dbmodels.ENTRIES_TABLE, queries...)
if err != nil {
return nil, err
} else if len(ids) == 0 {
return EmptyResultBaende(), nil
}
resultids := []any{}
for _, id := range ids {
resultids = append(resultids, id.ID)
}
e, err := dbmodels.Entries_IDs(app, resultids)
if err != nil {
return nil, err
}
entries = e
}
resultids := []any{}
for _, entry := range entries {
resultids = append(resultids, entry.Id)
}
entriesmap := make(map[string]*dbmodels.Entry)
for _, entry := range entries {
entriesmap[entry.Id] = entry
}
series, relations, err := Series_Entries(app, entries)
if err != nil {
return nil, err
}
seriesmap := make(map[string]*dbmodels.Series)
for _, s := range series {
seriesmap[s.Id] = s
}
relationsmap := make(map[string][]*dbmodels.REntriesSeries)
invrelationsmap := make(map[string][]*dbmodels.REntriesSeries)
for _, r := range relations {
invrelationsmap[r.Series()] = append(invrelationsmap[r.Series()], r)
relationsmap[r.Entry()] = append(relationsmap[r.Entry()], r)
}
agents, arelations, err := Agents_Entries_IDs(app, resultids)
if err != nil {
return nil, err
}
agentsmap := make(map[string]*dbmodels.Agent)
for _, a := range agents {
agentsmap[a.Id] = a
}
relationsagentsmap := make(map[string][]*dbmodels.REntriesAgents)
for _, r := range arelations {
relationsagentsmap[r.Entry()] = append(relationsagentsmap[r.Entry()], r)
}
placesids := []any{}
for _, entry := range entries {
for _, place := range entry.Places() {
placesids = append(placesids, place)
}
}
places, err := dbmodels.Places_IDs(app, placesids)
if err != nil {
return nil, err
}
placesmap := make(map[string]*dbmodels.Place)
for _, place := range places {
placesmap[place.Id] = place
}
hits := []string{}
if params.Sort == "series" {
dbmodels.Sort_Series_Title(series)
for _, s := range series {
hits = append(hits, s.Id)
}
} else {
dbmodels.Sort_Entries_Year_Title(entries)
for _, e := range entries {
hits = append(hits, e.Id)
}
}
return &SearchResultBaende{
Hits: hits,
Series: seriesmap,
Entries: entriesmap,
Places: placesmap,
Agents: agentsmap,
EntriesSeries: relationsmap,
SeriesEntries: invrelationsmap,
EntriesAgents: relationsagentsmap,
}, nil
}
func (r SearchResultBaende) Count() int {
return len(r.Entries)
}
func (r SearchResultBaende) SeriesCount() int {
return len(r.Series)
}
func Agents_Entries_IDs(app core.App, ids []any) ([]*dbmodels.Agent, []*dbmodels.REntriesAgents, error) {
relations, err := dbmodels.REntriesAgents_Entries(app, ids)
if err != nil {
return nil, nil, err
}
agentids := []any{}
for _, r := range relations {
agentids = append(agentids, r.Agent())
}
agents, err := dbmodels.Agents_IDs(app, agentids)
if err != nil {
return nil, nil, err
}
return agents, relations, nil
}

188
pages/suche_beitraege.go Normal file
View File

@@ -0,0 +1,188 @@
package pages
import (
"database/sql"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
"github.com/pocketbase/pocketbase/core"
)
const (
DEFAULT_PAGESIZE = 80
)
type SearchResultBeitraege struct {
Queries []dbmodels.FTS5QueryRequest
// these are the sorted IDs for hits
Hits []string
Entries map[string]*dbmodels.Entry // <- Key: Entry ID
Agents map[string]*dbmodels.Agent // <- Key: Agent IDs
Contents map[string][]*dbmodels.Content // <- Key: Entry ID, or year
ContentsAgents map[string][]*dbmodels.RContentsAgents // <- Key: Content ID
Pages []int
}
func EmptyResultBeitraege() *SearchResultBeitraege {
return &SearchResultBeitraege{
Hits: []string{},
Entries: make(map[string]*dbmodels.Entry),
Agents: make(map[string]*dbmodels.Agent),
Contents: make(map[string][]*dbmodels.Content),
ContentsAgents: make(map[string][]*dbmodels.RContentsAgents),
}
}
func NewSearchBeitraege(app core.App, params SearchParameters) (*SearchResultBeitraege, error) {
contents := []*dbmodels.Content{}
queries := params.FieldSetBeitraege()
if params.AlmString != "" {
e, err := dbmodels.Contents_MusenalmID(app, params.AlmString)
if err != nil && err == sql.ErrNoRows {
return EmptyResultBeitraege(), nil
} else if err != nil {
return nil, err
}
contents = append(contents, e)
} else {
if len(queries) == 0 {
return nil, ErrNoQuery
}
hits, err := dbmodels.FTS5Search(app, dbmodels.CONTENTS_TABLE, queries...)
if err != nil {
return nil, err
} else if len(hits) == 0 {
return EmptyResultBeitraege(), nil
}
ids := []any{}
for _, hit := range hits {
ids = append(ids, hit.ID)
}
cs, err := dbmodels.Contents_IDs(app, ids)
if err != nil {
return nil, err
}
contents = append(contents, cs...)
}
resultids := []any{}
resultentryids := []string{}
for _, content := range contents {
resultids = append(resultids, content.Id)
resultentryids = append(resultentryids, content.Entry())
}
entries, err := dbmodels.Entries_IDs(app, datatypes.ToAny(resultentryids))
if err != nil {
return nil, err
}
if params.Sort == "year" {
dbmodels.Sort_Entries_Year_Title(entries)
} else {
dbmodels.Sort_Entries_Title_Year(entries)
}
arels, err := dbmodels.RContentsAgents_Contents(app, resultids)
if err != nil {
return nil, err
}
aids := []any{}
for _, a := range arels {
aids = append(aids, a.Agent())
}
agents, err := dbmodels.Agents_IDs(app, aids)
if err != nil {
return nil, err
}
contentsmap := make(map[string][]*dbmodels.Content)
for _, c := range contents {
contentsmap[c.Entry()] = append(contentsmap[c.Entry()], c)
}
contentsagents := make(map[string][]*dbmodels.RContentsAgents)
for _, a := range arels {
contentsagents[a.Content()] = append(contentsagents[a.Content()], a)
}
agentsmap := make(map[string]*dbmodels.Agent)
for _, a := range agents {
agentsmap[a.Id] = a
}
entriesmap := make(map[string]*dbmodels.Entry)
for _, e := range entries {
entriesmap[e.Id] = e
}
hits := []string{}
for _, e := range entries {
hits = append(hits, e.Id)
}
pages := PagesEntries(hits, contentsmap, DEFAULT_PAGESIZE)
if params.Page < 1 || params.Page > len(pages) {
params.Page = 1
}
if params.Page == len(pages) {
hits = hits[pages[params.Page-1]:]
} else {
hits = hits[pages[params.Page-1]:pages[params.Page]]
}
return &SearchResultBeitraege{
Queries: queries,
Hits: hits,
Entries: entriesmap,
Agents: agentsmap,
Contents: contentsmap,
ContentsAgents: contentsagents,
Pages: pages,
}, nil
}
func (p *SearchResultBeitraege) CountEntries() int {
return len(p.Entries)
}
func (p *SearchResultBeitraege) Count() int {
cnt := 0
for _, c := range p.Contents {
cnt += len(c)
}
return cnt
}
func (p *SearchResultBeitraege) PagesCount() int {
return len(p.Pages) - 1
}
func PagesEntries[T any](hits []string, hitmap map[string][]*T, pagesize int) []int {
ret := []int{0}
m := 0
for i, hit := range hits {
m += len(hitmap[hit])
if m >= pagesize {
ret = append(ret, i)
m = 0
}
}
if m > 0 {
ret = append(ret, len(hits))
}
return ret
}

465
pages/suche_parameters.go Normal file
View File

@@ -0,0 +1,465 @@
package pages
import (
"fmt"
"strconv"
"strings"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/pocketbase/pocketbase/core"
)
var ErrNoQuery = fmt.Errorf("No query")
const (
SEARCH_PARAM_ALM_NR = "alm"
SEARCH_PARAM_TITLE = "title"
SEARCH_PARAM_PERSONS = "persons"
SEARCH_PARAM_ANNOTATIONS = "annotations"
SEARCH_PARAM_YEAR = "year"
BAENDE_PARAM_PLACES = "places"
BAENDE_PARAM_SERIES = "series"
BAENDE_PARAM_REFS = "references"
BEITRAEGE_PARAM_ENTRY = "entry"
BEITRAEGE_PARAM_INCIPT = "incipit"
)
type SearchParameters struct {
Parameters
Sort string // year, entry,
Annotations bool
Persons bool
Title bool
Series bool
Places bool
Refs bool
Year bool
Entry bool
Incipit bool
AnnotationsString string
PersonsString string
TitleString string
AlmString string
SeriesString string
PlacesString string
RefsString string
YearString string
EntryString string
IncipitString string
Page int
}
func NewSearchParameters(e *core.RequestEvent, p Parameters) (*SearchParameters, error) {
almstring := e.Request.URL.Query().Get(SEARCH_PARAM_ALM_NR + "string")
title := e.Request.URL.Query().Get(SEARCH_PARAM_TITLE) == "on"
titlestring := e.Request.URL.Query().Get(SEARCH_PARAM_TITLE + "string")
persons := e.Request.URL.Query().Get(SEARCH_PARAM_PERSONS) == "on"
personsstring := e.Request.URL.Query().Get(SEARCH_PARAM_PERSONS + "string")
annotations := e.Request.URL.Query().Get(SEARCH_PARAM_ANNOTATIONS) == "on"
annotationsstring := e.Request.URL.Query().Get(SEARCH_PARAM_ANNOTATIONS + "string")
year := e.Request.URL.Query().Get(SEARCH_PARAM_YEAR) == "on"
yearstring := e.Request.URL.Query().Get(SEARCH_PARAM_YEAR + "string")
series := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES) == "on"
seriesstring := e.Request.URL.Query().Get(BAENDE_PARAM_SERIES + "string")
places := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES) == "on"
placesstring := e.Request.URL.Query().Get(BAENDE_PARAM_PLACES + "string")
refs := e.Request.URL.Query().Get(BAENDE_PARAM_REFS) == "on"
refss := e.Request.URL.Query().Get(BAENDE_PARAM_REFS + "string")
if refss != "" {
refss = "\"" + refss + "\""
}
incipit := e.Request.URL.Query().Get(BEITRAEGE_PARAM_INCIPT) == "on"
incipitstring := e.Request.URL.Query().Get(BEITRAEGE_PARAM_INCIPT + "string")
entry := e.Request.URL.Query().Get(BEITRAEGE_PARAM_ENTRY) == "on"
entrystring := e.Request.URL.Query().Get(BEITRAEGE_PARAM_ENTRY + "string")
sort := e.Request.URL.Query().Get("sort")
if sort == "" {
sort = "year"
}
page := e.Request.URL.Query().Get("page")
if page == "" {
page = "1"
}
pageint, err := strconv.Atoi(page)
if err != nil {
return nil, err
}
return &SearchParameters{
Parameters: p,
Sort: sort,
Page: pageint,
// INFO: Common parameters
Title: title,
Persons: persons,
Annotations: annotations,
Year: year,
// INFO: Baende parameters
Places: places,
Refs: refs,
Series: series,
// INFO: Beitraege parameters
Entry: entry,
Incipit: incipit,
// INFO: Expanded search
AlmString: almstring,
TitleString: titlestring,
SeriesString: seriesstring,
PersonsString: personsstring,
PlacesString: placesstring,
RefsString: refss,
AnnotationsString: annotationsstring,
YearString: yearstring,
EntryString: entrystring,
IncipitString: incipitstring,
}, nil
}
func (p SearchParameters) AllSearchTermsBaende() string {
res := []string{}
res = append(res, p.includedParams(p.Query)...)
res = append(res, p.includedParams(p.AnnotationsString)...)
res = append(res, p.includedParams(p.PersonsString)...)
res = append(res, p.includedParams(p.TitleString)...)
res = append(res, p.includedParams(p.SeriesString)...)
res = append(res, p.includedParams(p.PlacesString)...)
res = append(res, p.includedParams(p.RefsString)...)
res = append(res, p.includedParams(p.YearString)...)
res = append(res, p.AlmString)
return strings.Join(res, " ")
}
func (p SearchParameters) AllSearchTermsBeitraege() string {
res := []string{}
res = append(res, p.includedParams(p.Query)...)
res = append(res, p.includedParams(p.AnnotationsString)...)
res = append(res, p.includedParams(p.PersonsString)...)
res = append(res, p.includedParams(p.TitleString)...)
res = append(res, p.includedParams(p.YearString)...)
res = append(res, p.includedParams(p.EntryString)...)
res = append(res, p.includedParams(p.IncipitString)...)
return strings.Join(res, " ")
}
func (p SearchParameters) includedParams(q string) []string {
res := []string{}
que := dbmodels.NormalizeQuery(q)
for _, qq := range que.Include {
res = append(res, qq)
}
for _, qq := range que.UnsafeI {
res = append(res, qq)
}
return res
}
func (p SearchParameters) ToQueryParamsBeitraege() string {
q := "?"
if p.Extended {
q += "extended=true"
}
if p.Query != "" {
q += fmt.Sprintf("q=%s", p.Query)
if p.Title {
q += "&title=on"
}
if p.Persons {
q += "&persons=on"
}
if p.Annotations {
q += "&annotations=on"
}
if p.Year {
q += "&year=on"
}
if p.Entry {
q += "&entry=on"
}
if p.Incipit {
q += "&incipit=on"
}
}
if p.YearString != "" {
q += fmt.Sprintf("&yearstring=%s", p.YearString)
}
if p.AnnotationsString != "" {
q += fmt.Sprintf("&annotationsstring=%s", p.AnnotationsString)
}
if p.PersonsString != "" {
q += fmt.Sprintf("&personsstring=%s", p.PersonsString)
}
if p.TitleString != "" {
q += fmt.Sprintf("&titlestring=%s", p.TitleString)
}
if p.EntryString != "" {
q += fmt.Sprintf("&entrystring=%s", p.EntryString)
}
if p.IncipitString != "" {
q += fmt.Sprintf("&incipitstring=%s", p.IncipitString)
}
return q
}
func (p SearchParameters) ToQueryParamsBaende() string {
q := "?"
// TODO: use variables, url escape
if p.Extended {
q += "extended=true"
}
if p.Query != "" {
q += fmt.Sprintf("q=%s", p.Query)
if p.Title {
q += "&title=on"
}
if p.Persons {
q += "&persons=on"
}
if p.Annotations {
q += "&annotations=on"
}
if p.Series {
q += "&series=on"
}
if p.Places {
q += "&places=on"
}
if p.Refs {
q += "&references=on"
}
if p.Year {
q += "&year=on"
}
}
if p.YearString != "" {
q += fmt.Sprintf("&yearstring=%s", p.YearString)
}
if p.AnnotationsString != "" {
q += fmt.Sprintf("&annotationsstring=%s", p.AnnotationsString)
}
if p.PersonsString != "" {
q += fmt.Sprintf("&personsstring=%s", p.PersonsString)
}
if p.TitleString != "" {
q += fmt.Sprintf("&titlestring=%s", p.TitleString)
}
if p.SeriesString != "" {
q += fmt.Sprintf("&seriesstring=%s", p.SeriesString)
}
if p.PlacesString != "" {
q += fmt.Sprintf("&placesstring=%s", p.PlacesString)
}
if p.RefsString != "" {
q += fmt.Sprintf("&refsstring=%s", p.RefsString)
}
return q
}
func (p SearchParameters) IsBeitraegeSearch() bool {
return p.Collection == "beitraege" && (p.Query != "" || p.AlmString != "" || p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.YearString != "" || p.EntryString != "" || p.IncipitString != "")
}
func (p SearchParameters) IsBaendeSearch() bool {
return p.Collection == "baende" && (p.Query != "" || p.AlmString != "" || p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != "")
}
func (p SearchParameters) FieldSetBeitraege() []dbmodels.FTS5QueryRequest {
ret := []dbmodels.FTS5QueryRequest{}
if p.Query != "" {
fields := []string{dbmodels.ID_FIELD}
if p.Title {
fields = append(fields, dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD)
}
if p.Persons {
fields = append(fields, dbmodels.RESPONSIBILITY_STMT_FIELD, dbmodels.AGENTS_TABLE)
}
if p.Annotations {
fields = append(fields, dbmodels.ANNOTATION_FIELD)
}
if p.Year {
fields = append(fields, dbmodels.YEAR_FIELD)
}
if p.Entry {
fields = append(fields, dbmodels.ENTRIES_TABLE)
}
if p.Incipit {
fields = append(fields, dbmodels.INCIPIT_STMT_FIELD)
}
que := p.NormalizeQuery()
req := dbmodels.IntoQueryRequests(fields, que)
ret = append(ret, req...)
}
if p.AnnotationsString != "" {
que := dbmodels.NormalizeQuery(p.AnnotationsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.ANNOTATION_FIELD}, que)
ret = append(ret, req...)
}
if p.PersonsString != "" {
que := dbmodels.NormalizeQuery(p.PersonsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.AGENTS_TABLE, dbmodels.RESPONSIBILITY_STMT_FIELD}, que)
ret = append(ret, req...)
}
if p.TitleString != "" {
que := dbmodels.NormalizeQuery(p.TitleString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD}, que)
ret = append(ret, req...)
}
if p.YearString != "" {
que := dbmodels.NormalizeQuery(p.YearString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.YEAR_FIELD}, que)
ret = append(ret, req...)
}
if p.EntryString != "" {
que := dbmodels.NormalizeQuery(p.EntryString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.ENTRIES_TABLE}, que)
ret = append(ret, req...)
}
if p.IncipitString != "" {
que := dbmodels.NormalizeQuery(p.IncipitString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.INCIPIT_STMT_FIELD}, que)
ret = append(ret, req...)
}
return ret
}
func (p SearchParameters) FieldSetBaende() []dbmodels.FTS5QueryRequest {
ret := []dbmodels.FTS5QueryRequest{}
if p.Query != "" {
fields := []string{dbmodels.ID_FIELD}
if p.Title {
// INFO: Preferred Title is not here to avoid hitting the Reihentitel
fields = append(fields,
dbmodels.TITLE_STMT_FIELD,
dbmodels.SUBTITLE_STMT_FIELD,
dbmodels.INCIPIT_STMT_FIELD,
dbmodels.VARIANT_TITLE_FIELD,
dbmodels.PARALLEL_TITLE_FIELD,
)
}
if p.Series {
fields = append(fields, dbmodels.SERIES_TABLE)
}
if p.Persons {
fields = append(fields, dbmodels.RESPONSIBILITY_STMT_FIELD, dbmodels.AGENTS_TABLE)
}
if p.Places {
fields = append(fields, dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD)
}
if p.Refs {
fields = append(fields, dbmodels.REFERENCES_FIELD)
}
if p.Annotations {
fields = append(fields, dbmodels.ANNOTATION_FIELD)
}
if p.Year {
fields = append(fields, dbmodels.YEAR_FIELD)
}
que := p.NormalizeQuery()
req := dbmodels.IntoQueryRequests(fields, que)
ret = append(ret, req...)
}
if p.AnnotationsString != "" {
que := dbmodels.NormalizeQuery(p.AnnotationsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.ANNOTATION_FIELD}, que)
ret = append(ret, req...)
}
if p.PersonsString != "" {
que := dbmodels.NormalizeQuery(p.PersonsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.AGENTS_TABLE, dbmodels.RESPONSIBILITY_STMT_FIELD}, que)
ret = append(ret, req...)
}
if p.TitleString != "" {
que := dbmodels.NormalizeQuery(p.TitleString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.TITLE_STMT_FIELD, dbmodels.SUBTITLE_STMT_FIELD, dbmodels.INCIPIT_STMT_FIELD, dbmodels.VARIANT_TITLE_FIELD, dbmodels.PARALLEL_TITLE_FIELD}, que)
ret = append(ret, req...)
}
if p.SeriesString != "" {
que := dbmodels.NormalizeQuery(p.SeriesString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.SERIES_TABLE}, que)
ret = append(ret, req...)
}
if p.PlacesString != "" {
que := dbmodels.NormalizeQuery(p.PlacesString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.PLACES_TABLE, dbmodels.PLACE_STMT_FIELD}, que)
ret = append(ret, req...)
}
if p.RefsString != "" {
que := dbmodels.NormalizeQuery(p.RefsString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.REFERENCES_FIELD}, que)
ret = append(ret, req...)
}
if p.YearString != "" {
que := dbmodels.NormalizeQuery(p.YearString)
req := dbmodels.IntoQueryRequests([]string{dbmodels.YEAR_FIELD}, que)
ret = append(ret, req...)
}
return ret
}
func (p SearchParameters) IsExtendedSearch() bool {
return p.AnnotationsString != "" || p.PersonsString != "" || p.TitleString != "" || p.AlmString != "" || p.SeriesString != "" || p.PlacesString != "" || p.RefsString != "" || p.YearString != "" || p.EntryString != "" || p.IncipitString != ""
}
func (p SearchParameters) NormalizeQuery() dbmodels.Query {
return dbmodels.NormalizeQuery(p.Query)
}
func (p SearchParameters) Prev() int {
if p.Page > 1 {
return p.Page - 1
}
return 1
}
func (p SearchParameters) Next() int {
return p.Page + 1
}