- every listed address of a record with more than one listed locale carries xhtml:link alternates for each locale plus x-default on the default locale (D-55 to D-57); no alternate for a missing translation - each page of records and the additional addresses go to the published contract.Filter in one batch: Exclude leaves the files with its reason, Group moves the address, LastMod seeds a new row (D-95, D-24) - classes.Listing implements contract.Listing and is published in Register
155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package classes
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/sm-sitemap-plugin/contract"
|
|
"git.golem15.com/golem15/sm-sitemap-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/signpost"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Listing implements contract.Listing over the slot table and the
|
|
// settings (D-95): other plugins read the site address, the index address,
|
|
// the file groups and each group's listed addresses without importing the
|
|
// sitemap plugin.
|
|
type Listing struct {
|
|
App *backpack.App
|
|
}
|
|
|
|
var _ contract.Listing = Listing{}
|
|
|
|
// The page sizes of Entries.
|
|
const (
|
|
defaultEntriesLimit = 100
|
|
maxEntriesLimit = 1000
|
|
)
|
|
|
|
func (l Listing) db(ctx context.Context) (*gorm.DB, error) {
|
|
if l.App == nil {
|
|
return nil, ErrNoDatabase
|
|
}
|
|
db, ok := l.App.Lookup[*gorm.DB]()
|
|
if !ok || db == nil {
|
|
return nil, ErrNoDatabase
|
|
}
|
|
return db.WithContext(ctx), nil
|
|
}
|
|
|
|
// Settings answers the site address, the index address, the default
|
|
// locale and the groups with listed addresses (by key), with their titles:
|
|
// a content type's saved HTML sitemap headings, else the titles of the
|
|
// published Filter's group of that key.
|
|
func (l Listing) Settings(ctx context.Context) (contract.ListingSettings, error) {
|
|
var out contract.ListingSettings
|
|
db, err := l.db(ctx)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
settings, err := LoadSettings(db)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
out.SiteURL = SiteURL(l.App, settings)
|
|
if out.SiteURL != "" {
|
|
out.IndexURL = out.SiteURL + "/sitemap.xml"
|
|
}
|
|
out.DefaultLocale = models.Text(settings.DefaultLocale)
|
|
var counts []struct {
|
|
Grp string `gorm:"column:grp"`
|
|
N int `gorm:"column:n"`
|
|
}
|
|
if err := db.Raw(`SELECT grp, COUNT(*) AS n FROM golem15_sitemap_urls WHERE excluded_reason = '' GROUP BY grp ORDER BY grp`).Scan(&counts).Error; err != nil {
|
|
return out, err
|
|
}
|
|
titles := map[string]map[string]string{}
|
|
if filter, ok := l.App.Lookup[contract.Filter](); ok && filter != nil {
|
|
groups, err := filter.Groups(ctx)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
for _, g := range groups {
|
|
if len(g.Titles) > 0 {
|
|
titles[g.Key] = g.Titles
|
|
}
|
|
}
|
|
}
|
|
if reg, ok := l.App.Lookup[*signpost.Registry](); ok && reg != nil {
|
|
rows, err := LoadTypeSettings(db)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
for _, typ := range reg.ContentTypes() {
|
|
cfg := EffectiveType(typ, rows)
|
|
if len(cfg.Titles) > 0 {
|
|
titles[cfg.Slug] = cfg.Titles
|
|
}
|
|
}
|
|
}
|
|
out.Groups = make([]contract.GroupInfo, 0, len(counts))
|
|
for _, c := range counts {
|
|
out.Groups = append(out.Groups, contract.GroupInfo{Key: c.Grp, Slug: c.Grp, Titles: titles[c.Grp], Count: c.N})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Entries pages a group's listed addresses in the order they were stored
|
|
// (row id); Next is the last row id of a page that has a successor. A
|
|
// Locale keeps the addresses of that locale and those without one.
|
|
func (l Listing) Entries(ctx context.Context, q contract.EntriesQuery) (contract.EntriesPage, error) {
|
|
page := contract.EntriesPage{Entries: []contract.Entry{}}
|
|
db, err := l.db(ctx)
|
|
if err != nil {
|
|
return page, err
|
|
}
|
|
settings, err := LoadSettings(db)
|
|
if err != nil {
|
|
return page, err
|
|
}
|
|
site := SiteURL(l.App, settings)
|
|
limit := q.Limit
|
|
if limit <= 0 {
|
|
limit = defaultEntriesLimit
|
|
}
|
|
limit = min(limit, maxEntriesLimit)
|
|
scope := func() *gorm.DB {
|
|
s := db.Model(&models.URL{}).Where("grp = ? AND excluded_reason = ''", q.Group)
|
|
if loc := strings.TrimSpace(q.Locale); loc != "" {
|
|
s = s.Where("locale = ? OR locale = ''", loc)
|
|
}
|
|
return s
|
|
}
|
|
var total int64
|
|
if err := scope().Count(&total).Error; err != nil {
|
|
return page, err
|
|
}
|
|
page.Total = int(total)
|
|
q2 := scope()
|
|
if after, err := strconv.ParseUint(strings.TrimSpace(q.After), 10, 64); err == nil && after > 0 {
|
|
q2 = q2.Where("id > ?", after)
|
|
}
|
|
var rows []models.URL
|
|
if err := q2.Order("id ASC").Limit(limit + 1).Find(&rows).Error; err != nil {
|
|
return page, err
|
|
}
|
|
if len(rows) > limit {
|
|
rows = rows[:limit]
|
|
page.Next = strconv.FormatUint(rows[len(rows)-1].ID, 10)
|
|
}
|
|
for _, r := range rows {
|
|
page.Entries = append(page.Entries, contract.Entry{
|
|
Group: r.Grp,
|
|
TypeKey: r.TypeKey,
|
|
RecordID: r.RecordID,
|
|
Locale: r.Locale,
|
|
URL: Absolute(site, r.URL),
|
|
Title: r.Title,
|
|
LastMod: r.LastMod,
|
|
})
|
|
}
|
|
return page, nil
|
|
}
|