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
}