mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
BUGFIX: keep fts5 tables syncronized
This commit is contained in:
@@ -230,3 +230,43 @@ func HasScans(contents []*dbmodels.Content) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateEntryFTS5(app core.App, entry *dbmodels.Entry) error {
|
||||||
|
if entry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load related data for FTS5
|
||||||
|
places := []*dbmodels.Place{}
|
||||||
|
for _, placeID := range entry.Places() {
|
||||||
|
place, err := dbmodels.Places_ID(app, placeID)
|
||||||
|
if err == nil && place != nil {
|
||||||
|
places = append(places, place)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agents := []*dbmodels.Agent{}
|
||||||
|
agentRelations, err := dbmodels.REntriesAgents_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, relation := range agentRelations {
|
||||||
|
agent, err := dbmodels.Agents_ID(app, relation.Agent())
|
||||||
|
if err == nil && agent != nil {
|
||||||
|
agents = append(agents, agent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
series := []*dbmodels.Series{}
|
||||||
|
seriesRelations, err := dbmodels.REntriesSeries_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, relation := range seriesRelations {
|
||||||
|
s, err := dbmodels.Series_ID(app, relation.Series())
|
||||||
|
if err == nil && s != nil {
|
||||||
|
series = append(series, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update entry and all related contents
|
||||||
|
return dbmodels.UpdateFTS5EntryAndRelatedContents(app, entry, places, agents, series)
|
||||||
|
}
|
||||||
|
|||||||
@@ -214,6 +214,18 @@ func (p *AlmanachEditPage) POSTSave(engine *templating.Engine, app core.App) Han
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index asynchronously
|
||||||
|
go func(appInstance core.App, entryID string) {
|
||||||
|
freshEntry, err := dbmodels.Entries_ID(appInstance, entryID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load entry for FTS5 update", "entry_id", entryID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := updateEntryFTS5(appInstance, freshEntry); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for entry", "entry_id", entryID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, entry.Id)
|
||||||
|
|
||||||
freshEntry, err := dbmodels.Entries_MusenalmID(app, id)
|
freshEntry, err := dbmodels.Entries_MusenalmID(app, id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
entry = freshEntry
|
entry = freshEntry
|
||||||
@@ -297,6 +309,13 @@ func (p *AlmanachEditPage) POSTDelete(engine *templating.Engine, app core.App) H
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete from FTS5 index asynchronously
|
||||||
|
go func(appInstance core.App, entryID string) {
|
||||||
|
if err := dbmodels.DeleteFTS5Entry(appInstance, entryID); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to delete FTS5 entry", "entry_id", entryID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, entry.Id)
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, map[string]any{
|
return e.JSON(http.StatusOK, map[string]any{
|
||||||
"success": true,
|
"success": true,
|
||||||
"redirect": "/suche/baende",
|
"redirect": "/suche/baende",
|
||||||
|
|||||||
@@ -151,6 +151,18 @@ func (p *AlmanachNewPage) POSTSave(engine *templating.Engine, app core.App) Hand
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index asynchronously
|
||||||
|
go func(appInstance core.App, entryID string) {
|
||||||
|
freshEntry, err := dbmodels.Entries_ID(appInstance, entryID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load entry for FTS5 update", "entry_id", entryID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := updateEntryFTS5(appInstance, freshEntry); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for new entry", "entry_id", entryID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, entry.Id)
|
||||||
|
|
||||||
redirect := "/"
|
redirect := "/"
|
||||||
if entry != nil {
|
if entry != nil {
|
||||||
redirect = "/almanach/" + strconv.Itoa(entry.MusenalmID()) + "/edit?saved_message=" + url.QueryEscape("Änderungen gespeichert.")
|
redirect = "/almanach/" + strconv.Itoa(entry.MusenalmID()) + "/edit?saved_message=" + url.QueryEscape("Änderungen gespeichert.")
|
||||||
|
|||||||
@@ -206,6 +206,18 @@ func (p *OrtEditPage) POST(engine *templating.Engine, app core.App) HandleFunc {
|
|||||||
return p.renderError(engine, app, e, "Speichern fehlgeschlagen.")
|
return p.renderError(engine, app, e, "Speichern fehlgeschlagen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index for place and all related entries asynchronously
|
||||||
|
go func(appInstance core.App, placeID string) {
|
||||||
|
freshPlace, err := dbmodels.Places_ID(appInstance, placeID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load place for FTS5 update", "place_id", placeID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := dbmodels.UpdateFTS5PlaceAndRelatedEntries(appInstance, freshPlace); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for place and related records", "place_id", placeID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, place.Id)
|
||||||
|
|
||||||
redirect := fmt.Sprintf("/ort/%s/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert."))
|
redirect := fmt.Sprintf("/ort/%s/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert."))
|
||||||
return e.Redirect(http.StatusSeeOther, redirect)
|
return e.Redirect(http.StatusSeeOther, redirect)
|
||||||
}
|
}
|
||||||
@@ -288,6 +300,20 @@ func (p *OrtEditPage) POSTDelete(engine *templating.Engine, app core.App) Handle
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete place from FTS5 and update all affected entries asynchronously
|
||||||
|
go func(appInstance core.App, placeID string, affectedEntries []*dbmodels.Entry) {
|
||||||
|
if err := dbmodels.DeleteFTS5Place(appInstance, placeID); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to delete place from FTS5", "place_id", placeID, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update FTS5 for all entries that had this place
|
||||||
|
for _, entry := range affectedEntries {
|
||||||
|
if err := updateEntryFTS5(appInstance, entry); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 for entry after place deletion", "entry_id", entry.Id, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(app, place.Id, entries)
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, map[string]any{
|
return e.JSON(http.StatusOK, map[string]any{
|
||||||
"success": true,
|
"success": true,
|
||||||
"redirect": "/orte",
|
"redirect": "/orte",
|
||||||
|
|||||||
@@ -130,6 +130,18 @@ func (p *OrtNewPage) POST(engine *templating.Engine, app core.App) HandleFunc {
|
|||||||
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
|
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index for new place (no related entries yet) asynchronously
|
||||||
|
go func(appInstance core.App, placeID string) {
|
||||||
|
freshPlace, err := dbmodels.Places_ID(appInstance, placeID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load place for FTS5 update", "place_id", placeID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := dbmodels.UpdateFTS5Place(appInstance, freshPlace); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for new place", "place_id", placeID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, createdPlace.Id)
|
||||||
|
|
||||||
redirect := fmt.Sprintf(
|
redirect := fmt.Sprintf(
|
||||||
"/ort/%s/edit?saved_message=%s",
|
"/ort/%s/edit?saved_message=%s",
|
||||||
createdPlace.Id,
|
createdPlace.Id,
|
||||||
|
|||||||
@@ -312,6 +312,18 @@ func (p *PersonEditPage) POST(engine *templating.Engine, app core.App) HandleFun
|
|||||||
return p.renderError(engine, app, e, "Speichern fehlgeschlagen.")
|
return p.renderError(engine, app, e, "Speichern fehlgeschlagen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index for agent and all related entries/contents asynchronously
|
||||||
|
go func(appInstance core.App, agentID string) {
|
||||||
|
freshAgent, err := dbmodels.Agents_ID(appInstance, agentID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load agent for FTS5 update", "agent_id", agentID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := dbmodels.UpdateFTS5AgentAndRelated(appInstance, freshAgent); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for agent and related records", "agent_id", agentID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, agent.Id)
|
||||||
|
|
||||||
redirect := fmt.Sprintf("/person/%s/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert."))
|
redirect := fmt.Sprintf("/person/%s/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert."))
|
||||||
return e.Redirect(http.StatusSeeOther, redirect)
|
return e.Redirect(http.StatusSeeOther, redirect)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,18 @@ func (p *PersonNewPage) POST(engine *templating.Engine, app core.App) HandleFunc
|
|||||||
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
|
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index for agent (no related records for new agent) asynchronously
|
||||||
|
go func(appInstance core.App, agentID string) {
|
||||||
|
freshAgent, err := dbmodels.Agents_ID(appInstance, agentID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load agent for FTS5 update", "agent_id", agentID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := dbmodels.UpdateFTS5Agent(appInstance, freshAgent); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for new agent", "agent_id", agentID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, createdAgent.Id)
|
||||||
|
|
||||||
redirect := fmt.Sprintf(
|
redirect := fmt.Sprintf(
|
||||||
"/person/%s/edit?saved_message=%s",
|
"/person/%s/edit?saved_message=%s",
|
||||||
createdAgent.Id,
|
createdAgent.Id,
|
||||||
|
|||||||
@@ -253,6 +253,19 @@ func (p *ReiheEditPage) POSTDelete(engine *templating.Engine, app core.App) Hand
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete series and entries from FTS5 asynchronously
|
||||||
|
go func(appInstance core.App, seriesID string, deletedEntries []*dbmodels.Entry) {
|
||||||
|
if err := dbmodels.DeleteFTS5Series(appInstance, seriesID); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to delete series from FTS5", "series_id", seriesID, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range deletedEntries {
|
||||||
|
if err := dbmodels.DeleteFTS5Entry(appInstance, entry.Id); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to delete FTS5 entry", "entry_id", entry.Id, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(app, series.Id, preferredEntries)
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, map[string]any{
|
return e.JSON(http.StatusOK, map[string]any{
|
||||||
"success": true,
|
"success": true,
|
||||||
"redirect": "/reihen",
|
"redirect": "/reihen",
|
||||||
@@ -405,6 +418,18 @@ func (p *ReiheEditPage) POST(engine *templating.Engine, app core.App) HandleFunc
|
|||||||
return p.renderError(engine, app, e, "Speichern fehlgeschlagen.")
|
return p.renderError(engine, app, e, "Speichern fehlgeschlagen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index for series and all related entries asynchronously
|
||||||
|
go func(appInstance core.App, seriesID string) {
|
||||||
|
freshSeries, err := dbmodels.Series_ID(appInstance, seriesID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load series for FTS5 update", "series_id", seriesID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := dbmodels.UpdateFTS5SeriesAndRelatedEntries(appInstance, freshSeries); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for series and related records", "series_id", seriesID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, series.Id)
|
||||||
|
|
||||||
redirect := fmt.Sprintf("/reihe/%s/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert."))
|
redirect := fmt.Sprintf("/reihe/%s/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert."))
|
||||||
return e.Redirect(http.StatusSeeOther, redirect)
|
return e.Redirect(http.StatusSeeOther, redirect)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,18 @@ func (p *ReiheNewPage) POST(engine *templating.Engine, app core.App) HandleFunc
|
|||||||
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
|
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update FTS5 index for new series (no related entries yet) asynchronously
|
||||||
|
go func(appInstance core.App, seriesID string) {
|
||||||
|
freshSeries, err := dbmodels.Series_ID(appInstance, seriesID)
|
||||||
|
if err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to load series for FTS5 update", "series_id", seriesID, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := dbmodels.UpdateFTS5Series(appInstance, freshSeries); err != nil {
|
||||||
|
appInstance.Logger().Error("Failed to update FTS5 index for new series", "series_id", seriesID, "error", err)
|
||||||
|
}
|
||||||
|
}(app, createdSeries.Id)
|
||||||
|
|
||||||
redirect := fmt.Sprintf(
|
redirect := fmt.Sprintf(
|
||||||
"/reihe/%d/edit?saved_message=%s",
|
"/reihe/%d/edit?saved_message=%s",
|
||||||
createdSeries.MusenalmID(),
|
createdSeries.MusenalmID(),
|
||||||
|
|||||||
303
dbmodels/fts5.go
303
dbmodels/fts5.go
@@ -562,3 +562,306 @@ func deleteTableContents(app core.App, table string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteFTS5Entry(app core.App, entryID string) error {
|
||||||
|
query := "DELETE FROM " + FTS5TableName(ENTRIES_TABLE) + " WHERE " + ID_FIELD + " = {:id}"
|
||||||
|
_, err := app.DB().NewQuery(query).Bind(dbx.Params{"id": entryID}).Execute()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5Entry(app core.App, entry *Entry, places []*Place, agents []*Agent, series []*Series) error {
|
||||||
|
if err := DeleteFTS5Entry(app, entry.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return InsertFTS5Entry(app, entry, places, agents, series)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5EntryAndRelatedContents(app core.App, entry *Entry, places []*Place, agents []*Agent, series []*Series) error {
|
||||||
|
// Update the entry itself
|
||||||
|
if err := UpdateFTS5Entry(app, entry, places, agents, series); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all contents that belong to this entry
|
||||||
|
contents, err := Contents_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, content := range contents {
|
||||||
|
// Load all agents for this content
|
||||||
|
contentAgents := []*Agent{}
|
||||||
|
agentRels, err := RContentsAgents_Content(app, content.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range agentRels {
|
||||||
|
agent, err := Agents_ID(app, rel.Agent())
|
||||||
|
if err == nil && agent != nil {
|
||||||
|
contentAgents = append(contentAgents, agent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := UpdateFTS5Content(app, content, entry, contentAgents); err != nil {
|
||||||
|
app.Logger().Error("Failed to update FTS5 for content", "content_id", content.Id, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFTS5Agent(app core.App, agentID string) error {
|
||||||
|
query := "DELETE FROM " + FTS5TableName(AGENTS_TABLE) + " WHERE " + ID_FIELD + " = {:id}"
|
||||||
|
_, err := app.DB().NewQuery(query).Bind(dbx.Params{"id": agentID}).Execute()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5Agent(app core.App, agent *Agent) error {
|
||||||
|
if err := DeleteFTS5Agent(app, agent.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return InsertFTS5Agent(app, agent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5AgentAndRelated(app core.App, agent *Agent) error {
|
||||||
|
// Update the agent itself
|
||||||
|
if err := UpdateFTS5Agent(app, agent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all entries related to this agent
|
||||||
|
entryRelations, err := REntriesAgents_Agent(app, agent.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, relation := range entryRelations {
|
||||||
|
entry, err := Entries_ID(app, relation.Entry())
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load entry for FTS5 update", "entry_id", relation.Entry(), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all related data for this entry
|
||||||
|
places := []*Place{}
|
||||||
|
for _, placeID := range entry.Places() {
|
||||||
|
place, err := Places_ID(app, placeID)
|
||||||
|
if err == nil && place != nil {
|
||||||
|
places = append(places, place)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agents := []*Agent{}
|
||||||
|
agentRels, err := REntriesAgents_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range agentRels {
|
||||||
|
ag, err := Agents_ID(app, rel.Agent())
|
||||||
|
if err == nil && ag != nil {
|
||||||
|
agents = append(agents, ag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
series := []*Series{}
|
||||||
|
seriesRels, err := REntriesSeries_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range seriesRels {
|
||||||
|
s, err := Series_ID(app, rel.Series())
|
||||||
|
if err == nil && s != nil {
|
||||||
|
series = append(series, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := UpdateFTS5Entry(app, entry, places, agents, series); err != nil {
|
||||||
|
app.Logger().Error("Failed to update FTS5 for entry", "entry_id", entry.Id, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all contents related to this agent
|
||||||
|
contentRelations, err := RContentsAgents_Agent(app, agent.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, relation := range contentRelations {
|
||||||
|
contents, err := Contents_IDs(app, []any{relation.Content()})
|
||||||
|
if err != nil || len(contents) == 0 {
|
||||||
|
app.Logger().Error("Failed to load content for FTS5 update", "content_id", relation.Content(), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content := contents[0]
|
||||||
|
|
||||||
|
// Load the parent entry
|
||||||
|
entry, err := Entries_ID(app, content.Entry())
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load entry for content FTS5 update", "entry_id", content.Entry(), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all agents for this content
|
||||||
|
agents := []*Agent{}
|
||||||
|
agentRels, err := RContentsAgents_Content(app, content.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range agentRels {
|
||||||
|
ag, err := Agents_ID(app, rel.Agent())
|
||||||
|
if err == nil && ag != nil {
|
||||||
|
agents = append(agents, ag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := UpdateFTS5Content(app, content, entry, agents); err != nil {
|
||||||
|
app.Logger().Error("Failed to update FTS5 for content", "content_id", content.Id, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFTS5Content(app core.App, contentID string) error {
|
||||||
|
query := "DELETE FROM " + FTS5TableName(CONTENTS_TABLE) + " WHERE " + ID_FIELD + " = {:id}"
|
||||||
|
_, err := app.DB().NewQuery(query).Bind(dbx.Params{"id": contentID}).Execute()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5Content(app core.App, content *Content, entry *Entry, agents []*Agent) error {
|
||||||
|
if err := DeleteFTS5Content(app, content.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return InsertFTS5Content(app, content, entry, agents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFTS5Place(app core.App, placeID string) error {
|
||||||
|
query := "DELETE FROM " + FTS5TableName(PLACES_TABLE) + " WHERE " + ID_FIELD + " = {:id}"
|
||||||
|
_, err := app.DB().NewQuery(query).Bind(dbx.Params{"id": placeID}).Execute()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5Place(app core.App, place *Place) error {
|
||||||
|
if err := DeleteFTS5Place(app, place.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return InsertFTS5Place(app, place)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5PlaceAndRelatedEntries(app core.App, place *Place) error {
|
||||||
|
// Update the place itself
|
||||||
|
if err := UpdateFTS5Place(app, place); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all entries that reference this place
|
||||||
|
entries := []*Entry{}
|
||||||
|
err := app.RecordQuery(ENTRIES_TABLE).
|
||||||
|
Where(dbx.NewExp(
|
||||||
|
PLACES_TABLE+" = {:id} OR (json_valid("+PLACES_TABLE+") = 1 AND EXISTS (SELECT 1 FROM json_each("+PLACES_TABLE+") WHERE value = {:id}))",
|
||||||
|
dbx.Params{"id": place.Id},
|
||||||
|
)).
|
||||||
|
All(&entries)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
for _, entry := range entries {
|
||||||
|
// Load all related data for this entry
|
||||||
|
places := []*Place{}
|
||||||
|
for _, placeID := range entry.Places() {
|
||||||
|
p, err := Places_ID(app, placeID)
|
||||||
|
if err == nil && p != nil {
|
||||||
|
places = append(places, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agents := []*Agent{}
|
||||||
|
agentRels, err := REntriesAgents_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range agentRels {
|
||||||
|
agent, err := Agents_ID(app, rel.Agent())
|
||||||
|
if err == nil && agent != nil {
|
||||||
|
agents = append(agents, agent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
series := []*Series{}
|
||||||
|
seriesRels, err := REntriesSeries_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range seriesRels {
|
||||||
|
s, err := Series_ID(app, rel.Series())
|
||||||
|
if err == nil && s != nil {
|
||||||
|
series = append(series, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update the entry itself, not contents (contents don't store place data)
|
||||||
|
if err := UpdateFTS5Entry(app, entry, places, agents, series); err != nil {
|
||||||
|
app.Logger().Error("Failed to update FTS5 for entry", "entry_id", entry.Id, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFTS5Series(app core.App, seriesID string) error {
|
||||||
|
query := "DELETE FROM " + FTS5TableName(SERIES_TABLE) + " WHERE " + ID_FIELD + " = {:id}"
|
||||||
|
_, err := app.DB().NewQuery(query).Bind(dbx.Params{"id": seriesID}).Execute()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5Series(app core.App, series *Series) error {
|
||||||
|
if err := DeleteFTS5Series(app, series.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return InsertFTS5Series(app, series)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFTS5SeriesAndRelatedEntries(app core.App, series *Series) error {
|
||||||
|
// Update the series itself
|
||||||
|
if err := UpdateFTS5Series(app, series); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all entries that reference this series
|
||||||
|
relations, err := REntriesSeries_Seriess(app, []any{series.Id})
|
||||||
|
if err == nil {
|
||||||
|
for _, relation := range relations {
|
||||||
|
entry, err := Entries_ID(app, relation.Entry())
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load entry for FTS5 update", "entry_id", relation.Entry(), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all related data for this entry
|
||||||
|
places := []*Place{}
|
||||||
|
for _, placeID := range entry.Places() {
|
||||||
|
place, err := Places_ID(app, placeID)
|
||||||
|
if err == nil && place != nil {
|
||||||
|
places = append(places, place)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agents := []*Agent{}
|
||||||
|
agentRels, err := REntriesAgents_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range agentRels {
|
||||||
|
agent, err := Agents_ID(app, rel.Agent())
|
||||||
|
if err == nil && agent != nil {
|
||||||
|
agents = append(agents, agent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allSeries := []*Series{}
|
||||||
|
seriesRels, err := REntriesSeries_Entry(app, entry.Id)
|
||||||
|
if err == nil {
|
||||||
|
for _, rel := range seriesRels {
|
||||||
|
s, err := Series_ID(app, rel.Series())
|
||||||
|
if err == nil && s != nil {
|
||||||
|
allSeries = append(allSeries, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update the entry itself, not contents (contents don't store series data)
|
||||||
|
if err := UpdateFTS5Entry(app, entry, places, agents, allSeries); err != nil {
|
||||||
|
app.Logger().Error("Failed to update FTS5 for entry", "entry_id", entry.Id, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ TODO danach:
|
|||||||
- Zeilenumbrüche in Reihen-Annotationen (EVTL. fix in TinyMCE)
|
- Zeilenumbrüche in Reihen-Annotationen (EVTL. fix in TinyMCE)
|
||||||
- Status: Auopsiert, Erfasst etc.
|
- Status: Auopsiert, Erfasst etc.
|
||||||
- Status farbig
|
- Status farbig
|
||||||
- Deprecated Fields display
|
|
||||||
- TinyMCE f. Annotationen
|
|
||||||
- Lsite verknüpfter Bände ind Inhalte wie in Personen (Reihen, Orte)
|
|
||||||
- Rolle anzeigen bei Reihen
|
|
||||||
- Lösch-Links in Liste, Übersicht u.s.w. (? CSRF-Token fehlt)
|
- Lösch-Links in Liste, Übersicht u.s.w. (? CSRF-Token fehlt)
|
||||||
- Löschen von Personen: werden relationen zu Inhalten mitgelöscht? optional inhalte löschen?
|
- Löschen von Personen: werden relationen zu Inhalten mitgelöscht? optional inhalte löschen?
|
||||||
|
- Display von Status u. Bearbeitungsvermerk in Almanach-Ansicht für eingeloggte Nutzer
|
||||||
|
- ACHTUNG! FTS5-Tabellen müssen beim Speichern (und löschen) synchronisiert werden!
|
||||||
|
- Hilfe-Texte für Felder
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -715,6 +715,41 @@ type AlmanachResult struct {
|
|||||||
<div id="physical"></div>
|
<div id="physical"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{- $deprecated := $model.result.Entry.Deprecated -}}
|
||||||
|
{{- if or $deprecated.Reihentitel $deprecated.Norm (gt $deprecated.BiblioID 0) $deprecated.Status $deprecated.Gesichtet $deprecated.Erfasst -}}
|
||||||
|
<details class="mt-4">
|
||||||
|
<summary class="flex items-center gap-2 text-lg font-bold text-stone-700 cursor-pointer select-none rounded-xs py-1 hover:bg-stone-50 hover:text-stone-900">
|
||||||
|
<i class="ri-history-line"></i>
|
||||||
|
<span>Abgekündigte Access-DB-Felder</span>
|
||||||
|
<i class="ri-arrow-down-s-line text-base legacy-toggle-icon"></i>
|
||||||
|
</summary>
|
||||||
|
<hr class="border-slate-400 mt-2 mb-3" />
|
||||||
|
<div class="text-xs leading-relaxed font-mono bg-slate-50 text-gray-600 border border-slate-200 rounded-xs px-3 py-2">
|
||||||
|
<div class="grid grid-cols-[7.5rem_1fr] gap-x-3 gap-y-1">
|
||||||
|
{{- if $deprecated.Reihentitel -}}
|
||||||
|
<div class="text-gray-500">Reihentitel</div>
|
||||||
|
<div>{{ $deprecated.Reihentitel }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $deprecated.Norm -}}
|
||||||
|
<div class="text-gray-500">Norm</div>
|
||||||
|
<div>{{ $deprecated.Norm }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if gt $deprecated.BiblioID 0 -}}
|
||||||
|
<div class="text-gray-500">Biblio-ID</div>
|
||||||
|
<div>{{ $deprecated.BiblioID }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $deprecated.Status -}}
|
||||||
|
<div class="text-gray-500">Status</div>
|
||||||
|
<div>{{- range $i, $s := $deprecated.Status -}}{{- if $i }}, {{ end -}}{{- $s -}}{{- end -}}</div>
|
||||||
|
{{- end -}}
|
||||||
|
<div class="text-gray-500">Gesichtet</div>
|
||||||
|
<div>{{ if $deprecated.Gesichtet }}Ja{{ else }}Nein{{ end }}</div>
|
||||||
|
<div class="text-gray-500">Erfasst</div>
|
||||||
|
<div>{{ if $deprecated.Erfasst }}Ja{{ else }}Nein{{ end }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{{- end -}}
|
||||||
<!-- Exemplare -->
|
<!-- Exemplare -->
|
||||||
<div class="">
|
<div class="">
|
||||||
<items-editor class="block mt-4">
|
<items-editor class="block mt-4">
|
||||||
|
|||||||
@@ -52,12 +52,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<h1 class="text-3xl font-bold">{{ $model.result.Agent.Name }}</h1>
|
<h1 class="text-3xl font-bold">{{ $model.result.Agent.Name }}</h1>
|
||||||
{{- if $model.result.Agent.Pseudonyms -}}
|
{{- if $model.result.Agent.Pseudonyms -}}
|
||||||
<p class="italic">
|
<p class="mt-1 italic">
|
||||||
auch:
|
auch:
|
||||||
<span class="">{{ $model.result.Agent.Pseudonyms }}</span>
|
<span class="">{{ $model.result.Agent.Pseudonyms }}</span>
|
||||||
</p>
|
</p>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<div>
|
<div class="mt-1">
|
||||||
<span class="">
|
<span class="">
|
||||||
{{ $model.result.Agent.BiographicalData }}
|
{{ $model.result.Agent.BiographicalData }}
|
||||||
</span>
|
</span>
|
||||||
@@ -80,6 +80,11 @@
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if $model.result.Agent.Annotation }}
|
||||||
|
<div class="max-w-[48rem] mt-1 annotation-content">
|
||||||
|
<span class="">{{ Safe $model.result.Agent.Annotation }}</span>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{- if .result.BResult -}}
|
{{- if .result.BResult -}}
|
||||||
|
|||||||
@@ -161,6 +161,14 @@
|
|||||||
@apply border-l-4 border-zinc-300 font-bold;
|
@apply border-l-4 border-zinc-300 font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.legacy-toggle-icon {
|
||||||
|
transition: transform 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] .legacy-toggle-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
.notifier {
|
.notifier {
|
||||||
@apply bg-stone-100 text-center text-base px-2.5 py-1 font-sans rounded;
|
@apply bg-stone-100 text-center text-base px-2.5 py-1 font-sans rounded;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user