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" "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), the HTML sitemap settings and the // IndexNow key file name (while IndexNow is on with a 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, _, _, err := groupTitles(ctx, l.App, db) if err != nil { return out, err } 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}) } out.HTMLEnabled = settings.HTMLEnabled out.HTMLPaths = settings.EffectiveHTMLPaths() out.HTMLPageSize = settings.EffectiveHTMLPageSize() out.KeyFile = KeyFileName(ActiveKey(settings)) 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 }