mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
+Show contents count in /baende list
This commit is contained in:
15
app/pb.go
15
app/pb.go
@@ -57,6 +57,7 @@ type BaendeCache struct {
|
|||||||
EntriesAgents map[string][]*dbmodels.REntriesAgents
|
EntriesAgents map[string][]*dbmodels.REntriesAgents
|
||||||
Items map[string][]*dbmodels.Item
|
Items map[string][]*dbmodels.Item
|
||||||
Users map[string]*dbmodels.User
|
Users map[string]*dbmodels.User
|
||||||
|
ContentsCount map[string]int
|
||||||
CachedAt time.Time
|
CachedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +94,9 @@ func (bc *BaendeCache) GetUsers() interface{} {
|
|||||||
return bc.Users
|
return bc.Users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bc *BaendeCache) GetContentsCount() interface{} {
|
||||||
|
return bc.ContentsCount
|
||||||
|
}
|
||||||
const (
|
const (
|
||||||
TEST_SUPERUSER_MAIL = "demo@example.com"
|
TEST_SUPERUSER_MAIL = "demo@example.com"
|
||||||
TEST_SUPERUSER_PASS = "password"
|
TEST_SUPERUSER_PASS = "password"
|
||||||
@@ -671,6 +675,16 @@ func (app *App) EnsureBaendeCache() (*BaendeCache, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load contents counts
|
||||||
|
contentsCount := map[string]int{}
|
||||||
|
if len(entryIDs) > 0 {
|
||||||
|
counts, err := dbmodels.CountContentsEntries(app.PB.App, entryIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contentsCount = counts
|
||||||
|
}
|
||||||
|
|
||||||
// Load users (editors)
|
// Load users (editors)
|
||||||
usersMap := map[string]*dbmodels.User{}
|
usersMap := map[string]*dbmodels.User{}
|
||||||
editorIDs := map[string]struct{}{}
|
editorIDs := map[string]struct{}{}
|
||||||
@@ -704,6 +718,7 @@ func (app *App) EnsureBaendeCache() (*BaendeCache, error) {
|
|||||||
EntriesAgents: entryAgentsMap,
|
EntriesAgents: entryAgentsMap,
|
||||||
Items: itemsMap,
|
Items: itemsMap,
|
||||||
Users: usersMap,
|
Users: usersMap,
|
||||||
|
ContentsCount: contentsCount,
|
||||||
CachedAt: time.Now(),
|
CachedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const (
|
|||||||
URL_BAENDE_DELETE = "/baende/delete-info/{id}"
|
URL_BAENDE_DELETE = "/baende/delete-info/{id}"
|
||||||
TEMPLATE_BAENDE = "/baende/"
|
TEMPLATE_BAENDE = "/baende/"
|
||||||
URL_BAENDE_DETAILS = "/baende/details/{id}"
|
URL_BAENDE_DETAILS = "/baende/details/{id}"
|
||||||
BAENDE_PAGE_SIZE = 150
|
BAENDE_PAGE_SIZE = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -55,6 +55,7 @@ type BaendeResult struct {
|
|||||||
EntriesAgents map[string][]*dbmodels.REntriesAgents
|
EntriesAgents map[string][]*dbmodels.REntriesAgents
|
||||||
Items map[string][]*dbmodels.Item
|
Items map[string][]*dbmodels.Item
|
||||||
Users map[string]*dbmodels.User
|
Users map[string]*dbmodels.User
|
||||||
|
ContentsCount map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaendeDetailsResult struct {
|
type BaendeDetailsResult struct {
|
||||||
@@ -136,6 +137,15 @@ func (p *BaendePage) handleRow(engine *templating.Engine, app core.App) HandleFu
|
|||||||
app.Logger().Error("Failed to get items for entry", "error", err)
|
app.Logger().Error("Failed to get items for entry", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contents, err := dbmodels.Contents_Entry(app, entry.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to get contents for entry", "error", err)
|
||||||
|
}
|
||||||
|
contentsCount := 0
|
||||||
|
if contents != nil {
|
||||||
|
contentsCount = len(contents)
|
||||||
|
}
|
||||||
|
|
||||||
var editorUser *dbmodels.User
|
var editorUser *dbmodels.User
|
||||||
if editorID := entry.Editor(); editorID != "" {
|
if editorID := entry.Editor(); editorID != "" {
|
||||||
user, err := dbmodels.Users_ID(app, editorID)
|
user, err := dbmodels.Users_ID(app, editorID)
|
||||||
@@ -147,11 +157,12 @@ func (p *BaendePage) handleRow(engine *templating.Engine, app core.App) HandleFu
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"entry": entry,
|
"entry": entry,
|
||||||
"items": items,
|
"items": items,
|
||||||
"editor_user": editorUser,
|
"editor_user": editorUser,
|
||||||
"is_admin": req.IsAdmin(),
|
"contents_count": contentsCount,
|
||||||
"csrf_token": req.Session().Token,
|
"is_admin": req.IsAdmin(),
|
||||||
|
"csrf_token": req.Session().Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
return engine.Response200(e, "/baende/row/", data, "fragment")
|
return engine.Response200(e, "/baende/row/", data, "fragment")
|
||||||
@@ -362,6 +373,11 @@ func (p *BaendePage) buildResultData(app core.App, ma pagemodels.IApp, e *core.R
|
|||||||
return data, fmt.Errorf("failed to get users from cache")
|
return data, fmt.Errorf("failed to get users from cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentsCount, ok := cacheInterface.GetContentsCount().(map[string]int)
|
||||||
|
if !ok {
|
||||||
|
return data, fmt.Errorf("failed to get contents count from cache")
|
||||||
|
}
|
||||||
|
|
||||||
// Apply search/letter/filters
|
// Apply search/letter/filters
|
||||||
filteredEntries := allEntries
|
filteredEntries := allEntries
|
||||||
if search != "" {
|
if search != "" {
|
||||||
@@ -472,6 +488,7 @@ func (p *BaendePage) buildResultData(app core.App, ma pagemodels.IApp, e *core.R
|
|||||||
EntriesAgents: entryAgentsMap,
|
EntriesAgents: entryAgentsMap,
|
||||||
Items: itemsMap,
|
Items: itemsMap,
|
||||||
Users: usersMap,
|
Users: usersMap,
|
||||||
|
ContentsCount: contentsCount,
|
||||||
}
|
}
|
||||||
data["offset"] = offset
|
data["offset"] = offset
|
||||||
data["total_count"] = totalCount
|
data["total_count"] = totalCount
|
||||||
|
|||||||
@@ -294,6 +294,31 @@ func Items_Entries(app core.App, ids []any) ([]*Item, error) {
|
|||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EntryCount struct {
|
||||||
|
Count int `db:"count"`
|
||||||
|
ID string `db:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CountContentsEntries(app core.App, ids []any) (map[string]int, error) {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return map[string]int{}, nil
|
||||||
|
}
|
||||||
|
counts := []EntryCount{}
|
||||||
|
err := app.RecordQuery(CONTENTS_TABLE).
|
||||||
|
Select("count(*) as count, " + ENTRIES_TABLE + " as id").
|
||||||
|
Where(dbx.HashExp{ENTRIES_TABLE: ids}).
|
||||||
|
GroupBy(ENTRIES_TABLE).
|
||||||
|
All(&counts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret := make(map[string]int, len(counts))
|
||||||
|
for _, c := range counts {
|
||||||
|
ret[c.ID] = c.Count
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Contents_MusenalmID(app core.App, id string) (*Content, error) {
|
func Contents_MusenalmID(app core.App, id string) (*Content, error) {
|
||||||
ret, err := TableByField[Content](app, CONTENTS_TABLE, MUSENALMID_FIELD, id)
|
ret, err := TableByField[Content](app, CONTENTS_TABLE, MUSENALMID_FIELD, id)
|
||||||
return &ret, err
|
return &ret, err
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type BaendeCacheInterface interface {
|
|||||||
GetEntriesAgents() interface{} // Returns map[string][]*dbmodels.REntriesAgents
|
GetEntriesAgents() interface{} // Returns map[string][]*dbmodels.REntriesAgents
|
||||||
GetItems() interface{} // Returns map[string][]*dbmodels.Item
|
GetItems() interface{} // Returns map[string][]*dbmodels.Item
|
||||||
GetUsers() interface{} // Returns map[string]*dbmodels.User
|
GetUsers() interface{} // Returns map[string]*dbmodels.User
|
||||||
|
GetContentsCount() interface{} // Returns map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
type IApp interface {
|
type IApp interface {
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ class="container-normal font-sans mt-10">
|
|||||||
"
|
"
|
||||||
:disabled="loading">
|
:disabled="loading">
|
||||||
<i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i>
|
<i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i>
|
||||||
<span x-text="loading ? 'Lädt...' : 'Weitere 150 laden'"></span>
|
<span x-text="loading ? 'Lädt...' : 'Weitere 100 laden'"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -157,6 +157,14 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="data-tip">Bearbeiten</div>
|
<div class="data-tip">Bearbeiten</div>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}/contents/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
|
<i class="ri-file-list-3-line"></i>
|
||||||
|
{{- $count := index $model.result.ContentsCount $entry.Id -}}
|
||||||
|
<span>{{ if $count }}{{ $count }}{{ else }}0{{ end }}</span>
|
||||||
|
</a>
|
||||||
|
<div class="data-tip">Beiträge bearbeiten</div>
|
||||||
|
</tool-tip>
|
||||||
<tool-tip position="top" class="inline">
|
<tool-tip position="top" class="inline">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
{{- if $entry.References -}}
|
{{- if $entry.References -}}
|
||||||
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs text-slate-700 max-w-[10rem] whitespace-normal break-words" title="{{ $entry.References }}">{{ $entry.References }}</span>
|
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs text-slate-700 max-w-[10rem] whitespace-normal break-words" title="{{ $entry.References }}">{{ $entry.References }}</span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<div class="flex flex-wrap items-center gap-1.5 pt-1">
|
<div class="flex flex-wrap items-center gap-1.5 pt-1">
|
||||||
<tool-tip position="top" class="inline">
|
<tool-tip position="top" class="inline">
|
||||||
<a href="/almanach/{{ $entry.MusenalmID }}" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
<a href="/almanach/{{ $entry.MusenalmID }}" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
<i class="ri-eye-line"></i>
|
<i class="ri-eye-line"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="data-tip">Ansehen</div>
|
<div class="data-tip">Ansehen</div>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
{{- if (IsAdminOrEditor $model.request.user) -}}
|
{{- if (IsAdminOrEditor $model.request.user) -}}
|
||||||
@@ -27,6 +27,14 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="data-tip">Bearbeiten</div>
|
<div class="data-tip">Bearbeiten</div>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}/contents/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
|
<i class="ri-file-list-3-line"></i>
|
||||||
|
{{- $count := index $model.result.ContentsCount $entry.Id -}}
|
||||||
|
<span>{{ if $count }}{{ $count }}{{ else }}0{{ end }}</span>
|
||||||
|
</a>
|
||||||
|
<div class="data-tip">Beiträge bearbeiten</div>
|
||||||
|
</tool-tip>
|
||||||
<tool-tip position="top" class="inline">
|
<tool-tip position="top" class="inline">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"
|
"
|
||||||
:disabled="loading">
|
:disabled="loading">
|
||||||
<i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i>
|
<i class="ri-arrow-down-line" :class="{ 'spinning': loading }"></i>
|
||||||
<span x-text="loading ? 'Lädt...' : 'Weitere 150 laden'"></span>
|
<span x-text="loading ? 'Lädt...' : 'Weitere 100 laden'"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,19 @@
|
|||||||
<div class="data-tip">Ansehen</div>
|
<div class="data-tip">Ansehen</div>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
{{- if .is_admin -}}
|
{{- if .is_admin -}}
|
||||||
<tool-tip position="top" class="inline">
|
<tool-tip position="top" class="inline">
|
||||||
<a href="/almanach/{{ $entry.MusenalmID }}/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
<a href="/almanach/{{ $entry.MusenalmID }}/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
<i class="ri-edit-line"></i>
|
<i class="ri-edit-line"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="data-tip">Bearbeiten</div>
|
<div class="data-tip">Bearbeiten</div>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}/contents/edit" onclick="event.stopPropagation();" hx-target="body" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
|
<i class="ri-file-list-3-line"></i>
|
||||||
|
<span>{{ if .contents_count }}{{ .contents_count }}{{ else }}0{{ end }}</span>
|
||||||
|
</a>
|
||||||
|
<div class="data-tip">Beiträge bearbeiten</div>
|
||||||
|
</tool-tip>
|
||||||
<tool-tip position="top" class="inline">
|
<tool-tip position="top" class="inline">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user