GoBlog/postsDb.go

769 lines
24 KiB
Go
Raw Permalink Normal View History

package main
import (
"database/sql"
"errors"
2020-10-06 17:07:48 +00:00
"fmt"
"strconv"
"strings"
2020-10-06 17:07:48 +00:00
"text/template"
"time"
2020-10-06 17:07:48 +00:00
"github.com/araddon/dateparse"
2022-03-16 07:28:03 +00:00
"github.com/samber/lo"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/builderpool"
)
func (a *goBlog) checkPost(p *post, new bool) (err error) {
if p == nil {
return errors.New("no post")
}
now := time.Now().Local()
nowString := now.Format(time.RFC3339)
2022-12-26 18:52:06 +00:00
// Add parameters map
if p.Parameters == nil {
p.Parameters = map[string][]string{}
}
// Maybe add blog
if p.Blog == "" {
p.Blog = a.cfg.DefaultBlog
}
// Check blog
if _, ok := a.cfg.Blogs[p.Blog]; !ok {
return errors.New("blog doesn't exist")
}
// Maybe add section
if p.Path == "" && p.Section == "" {
// Has no path or section -> default section
2022-12-14 15:03:54 +00:00
p.Section = a.getBlogFromPost(p).DefaultSection
}
// Check section
if p.Section != "" {
2022-12-14 15:03:54 +00:00
if _, ok := a.getBlogFromPost(p).Sections[p.Section]; !ok {
return errors.New("section doesn't exist")
}
}
// Fix and check date strings
if p.Published != "" {
2020-12-16 20:24:53 +00:00
p.Published, err = toLocal(p.Published)
if err != nil {
return err
}
}
if p.Updated != "" {
2020-12-16 20:24:53 +00:00
p.Updated, err = toLocal(p.Updated)
if err != nil {
return err
}
}
// Maybe set published date
if new && p.Published == "" && p.Section != "" {
// Has no published date, but section -> published now
p.Published = nowString
}
// Maybe set updated date
if !new && p.Published != "" {
if published, err := dateparse.ParseLocal(p.Published); err == nil && now.After(published) {
// Has published date in the past, so add updated date
p.Updated = nowString
}
2022-12-25 20:06:23 +00:00
} else if !new && p.Updated != "" {
if updated, err := dateparse.ParseLocal(p.Updated); err == nil && now.After(updated) {
// Has updated date in the past, so add new updated date
p.Updated = nowString
}
}
// Fix content
p.Content = strings.TrimSuffix(strings.TrimPrefix(p.Content, "\n"), "\n")
2021-01-15 20:56:46 +00:00
// Check status
if p.Status == statusNil {
2021-01-15 20:56:46 +00:00
p.Status = statusPublished
if p.Published != "" {
// If published time is in the future, set status to scheduled
publishedTime, err := dateparse.ParseLocal(p.Published)
if err != nil {
return err
}
if publishedTime.After(now) {
p.Status = statusScheduled
}
}
} else if !validPostStatus(p.Status) {
return errors.New("invalid post status")
2021-01-15 20:56:46 +00:00
}
// Check visibility
if p.Visibility == visibilityNil {
p.Visibility = visibilityPublic
} else if !validPostVisibility(p.Visibility) {
return errors.New("invalid post visibility")
}
2020-10-14 16:31:05 +00:00
// Cleanup params
for pk, pvs := range p.Parameters {
pvs = lo.Filter(pvs, func(s string, _ int) bool { return s != "" })
if len(pvs) == 0 {
delete(p.Parameters, pk)
2020-10-14 16:31:05 +00:00
continue
}
p.Parameters[pk] = pvs
}
// Add context for replies and likes
if new {
a.addReplyTitleAndContext(p)
a.addLikeTitleAndContext(p)
}
2020-10-06 17:07:48 +00:00
// Check path
2020-12-23 15:53:10 +00:00
if p.Path != "/" {
p.Path = strings.TrimSuffix(p.Path, "/")
}
2020-10-06 17:07:48 +00:00
if p.Path == "" {
published, parseErr := dateparse.ParseLocal(p.Published)
if parseErr != nil {
published = now
2020-10-06 17:07:48 +00:00
}
if p.Slug == "" {
p.Slug = fmt.Sprintf("%v-%02d-%02d-%v", published.Year(), int(published.Month()), published.Day(), randomString(5))
2020-10-06 17:07:48 +00:00
}
2021-07-30 13:43:13 +00:00
pathTmplString := defaultIfEmpty(
2022-12-14 15:03:54 +00:00
a.getBlogFromPost(p).Sections[p.Section].PathTemplate,
2021-07-30 13:43:13 +00:00
"{{printf \""+a.getRelativePath(p.Blog, "/%v/%02d/%02d/%v")+"\" .Section .Year .Month .Slug}}",
)
2020-10-06 17:07:48 +00:00
pathTmpl, err := template.New("location").Parse(pathTmplString)
if err != nil {
return errors.New("failed to parse location template")
}
pathBuffer := bufferpool.Get()
defer bufferpool.Put(pathBuffer)
2022-03-16 07:28:03 +00:00
err = pathTmpl.Execute(pathBuffer, map[string]any{
2021-06-11 06:24:41 +00:00
"BlogPath": a.getRelativePath(p.Blog, ""),
2020-11-01 17:37:21 +00:00
"Year": published.Year(),
"Month": int(published.Month()),
"Day": published.Day(),
"Slug": p.Slug,
"Section": p.Section,
})
2020-10-06 17:07:48 +00:00
if err != nil {
return errors.New("failed to execute location template")
}
p.Path = pathBuffer.String()
}
if p.Path != "" && !strings.HasPrefix(p.Path, "/") {
return errors.New("wrong path")
}
return nil
}
func (a *goBlog) createPost(p *post) error {
return a.createOrReplacePost(p, &postCreationOptions{new: true})
2021-01-15 20:56:46 +00:00
}
func (a *goBlog) replacePost(p *post, oldPath string, oldStatus postStatus, oldVisibility postVisibility) error {
return a.createOrReplacePost(p, &postCreationOptions{new: false, oldPath: oldPath, oldStatus: oldStatus, oldVisibility: oldVisibility})
}
2021-01-15 20:56:46 +00:00
type postCreationOptions struct {
new bool
oldPath string
oldStatus postStatus
oldVisibility postVisibility
}
func (a *goBlog) createOrReplacePost(p *post, o *postCreationOptions) error {
// Check post
if err := a.checkPost(p, o.new); err != nil {
return err
}
// Save to db
if err := a.db.savePost(p, o); err != nil {
return err
}
2021-08-07 12:39:23 +00:00
// Reload post from database
p, err := a.getPost(p.Path)
if err != nil {
// Failed to reload post from database
return err
}
// Trigger hooks
if p.Status == statusPublished && (p.Visibility == visibilityPublic || p.Visibility == visibilityUnlisted) {
if o.new || o.oldStatus == statusScheduled || (o.oldStatus != statusPublished && o.oldVisibility != visibilityPublic && o.oldVisibility != visibilityUnlisted) {
defer a.postPostHooks(p)
} else {
defer a.postUpdateHooks(p)
}
}
2021-07-17 07:33:44 +00:00
// Purge cache
a.cache.purge()
2022-04-17 05:40:02 +00:00
a.deleteReactionsCache(p.Path)
2021-07-17 07:33:44 +00:00
return nil
}
// Save check post to database
func (db *database) savePost(p *post, o *postCreationOptions) error {
// Check
if !o.new && o.oldPath == "" {
return errors.New("old path required")
}
// Lock post creation
db.pcm.Lock()
defer db.pcm.Unlock()
2021-05-29 11:32:00 +00:00
// Build SQL
sqlBuilder := builderpool.Get()
defer builderpool.Put(sqlBuilder)
2022-03-16 07:28:03 +00:00
var sqlArgs = []any{dbNoCache}
// Start transaction
sqlBuilder.WriteString("begin;")
2022-04-16 19:42:09 +00:00
// Update or create post
if o.new {
// New post, create it
2023-11-21 09:37:06 +00:00
sqlBuilder.WriteString("insert or rollback into posts (path, content, published, updated, blog, section, status, visibility, priority) values (?, ?, ?, ?, ?, ?, ?, ?, ?);")
sqlArgs = append(sqlArgs, p.Path, p.Content, toUTCSafe(p.Published), toUTCSafe(p.Updated), p.Blog, p.Section, p.Status, p.Visibility, p.Priority)
2022-04-16 19:42:09 +00:00
} else {
// Delete post parameters
sqlBuilder.WriteString("delete from post_parameters where path = ?;")
sqlArgs = append(sqlArgs, o.oldPath)
// Update old post
2023-11-21 09:37:06 +00:00
sqlBuilder.WriteString("update or rollback posts set path = ?, content = ?, published = ?, updated = ?, blog = ?, section = ?, status = ?, visibility = ?, priority = ? where path = ?;")
sqlArgs = append(sqlArgs, p.Path, p.Content, toUTCSafe(p.Published), toUTCSafe(p.Updated), p.Blog, p.Section, p.Status, p.Visibility, p.Priority, o.oldPath)
2022-04-16 19:42:09 +00:00
}
2021-05-29 11:32:00 +00:00
// Insert post parameters
for param, value := range p.Parameters {
for _, value := range lo.Filter(value, loStringNotEmpty) {
2023-11-21 09:37:06 +00:00
sqlBuilder.WriteString("insert or rollback into post_parameters (path, parameter, value) values (?, ?, ?);")
sqlArgs = append(sqlArgs, p.Path, param, value)
}
}
// Commit transaction
sqlBuilder.WriteString("commit;")
2021-05-29 11:32:00 +00:00
// Execute
if _, err := db.Exec(sqlBuilder.String(), sqlArgs...); err != nil {
if strings.Contains(err.Error(), "UNIQUE constraint failed: posts.path") {
return errors.New("post already exists at given path")
}
return err
}
// Update FTS index
db.rebuildFTSIndex()
return nil
}
func (a *goBlog) deletePost(path string) error {
if path == "" {
return errors.New("path required")
}
// Lock post creation
a.db.pcm.Lock()
defer a.db.pcm.Unlock()
// Check if post exists
p, err := a.getPost(path)
if err != nil {
return err
}
// Post exists, check if it's already marked as deleted
if p.Deleted() {
// Post is already marked as deleted, delete it from database
if _, err = a.db.Exec(
`begin; delete from posts where path = ?; insert or ignore into deleted (path) values (?); commit;`,
dbNoCache, p.Path, p.Path, p.Path,
); err != nil {
return err
}
// Rebuild FTS index
a.db.rebuildFTSIndex()
// Purge cache
a.cache.purge()
2022-04-17 05:40:02 +00:00
a.deleteReactionsCache(p.Path)
} else {
// Update post status
p.Status = p.Status + statusDeletedSuffix
// Add parameter
deletedTime := utcNowString()
if p.Parameters == nil {
p.Parameters = map[string][]string{}
}
p.Parameters["deleted"] = []string{deletedTime}
// Mark post as deleted
if _, err = a.db.Exec(
`begin; update posts set status = ? where path = ?; delete from post_parameters where path = ? and parameter = 'deleted'; insert into post_parameters (path, parameter, value) values (?, 'deleted', ?); commit;`,
dbNoCache, p.Status, p.Path, p.Path, p.Path, deletedTime,
); err != nil {
return err
}
// Rebuild FTS index
a.db.rebuildFTSIndex()
// Purge cache
a.cache.purge()
// Trigger hooks
a.postDeleteHooks(p)
}
2021-07-17 07:33:44 +00:00
return nil
}
func (a *goBlog) undeletePost(path string) error {
if path == "" {
return errors.New("path required")
}
// Lock post creation
2021-08-05 06:09:34 +00:00
a.db.pcm.Lock()
defer a.db.pcm.Unlock()
// Check if post exists
2021-08-05 06:09:34 +00:00
p, err := a.getPost(path)
2020-11-08 22:04:32 +00:00
if err != nil {
return err
2020-11-08 22:04:32 +00:00
}
// Post exists, update status and parameters
p.Status = postStatus(strings.TrimSuffix(string(p.Status), string(statusDeletedSuffix)))
// Remove parameter
p.Parameters["deleted"] = nil
// Update database
if _, err = a.db.Exec(
`begin; update posts set status = ? where path = ?; delete from post_parameters where path = ? and parameter = 'deleted'; commit;`,
dbNoCache, p.Status, p.Path, p.Path,
); err != nil {
return err
2020-11-15 10:34:48 +00:00
}
// Rebuild FTS index
2021-08-05 06:09:34 +00:00
a.db.rebuildFTSIndex()
// Purge cache
a.cache.purge()
// Trigger hooks
a.postUndeleteHooks(p)
return nil
}
2021-09-07 20:16:28 +00:00
func (db *database) replacePostParam(path, param string, values []string) error {
2022-04-15 18:53:19 +00:00
// Filter empty values
values = lo.Filter(values, loStringNotEmpty)
2021-09-07 20:16:28 +00:00
// Lock post creation
db.pcm.Lock()
defer db.pcm.Unlock()
// Build SQL
sqlBuilder := builderpool.Get()
defer builderpool.Put(sqlBuilder)
2022-03-16 07:28:03 +00:00
var sqlArgs = []any{dbNoCache}
2021-09-07 20:16:28 +00:00
// Start transaction
sqlBuilder.WriteString("begin;")
// Delete old post
sqlBuilder.WriteString("delete from post_parameters where path = ? and parameter = ?;")
sqlArgs = append(sqlArgs, path, param)
// Insert new post parameters
for _, value := range values {
2022-04-15 18:53:19 +00:00
sqlBuilder.WriteString("insert into post_parameters (path, parameter, value) values (?, ?, ?);")
sqlArgs = append(sqlArgs, path, param, value)
2021-09-07 20:16:28 +00:00
}
// Commit transaction
sqlBuilder.WriteString("commit;")
// Execute
_, err := db.Exec(sqlBuilder.String(), sqlArgs...)
2022-04-15 18:53:19 +00:00
if err != nil {
2021-09-07 20:16:28 +00:00
return err
}
// Update FTS index
db.rebuildFTSIndex()
return nil
}
type postsRequestConfig struct {
search string
blog string
path string
limit int
offset int
sections []string
status []postStatus
visibility []postVisibility
taxonomy *configTaxonomy
taxonomyValue string
anyParams []string // filter for posts that have any of these parameters (with non-empty values)
allParams []string // filter for posts that have all these parameters (with non-empty values)
allParamValues []string // ... with exactly these values
parameter string // filter for posts that have this parameter (with non-empty value)
parameterValue string // ... with exactly this value
excludeParameter string // exclude posts that have this parameter (with non-empty value)
excludeParameterValue string // ... with exactly this value
publishedYear, publishedMonth, publishedDay int
2021-12-11 18:43:40 +00:00
publishedBefore time.Time
randomOrder bool
priorityOrder bool
fetchWithoutParams bool // fetch posts without parameters
fetchParams []string // only fetch these parameters
withoutRenderedTitle bool // fetch posts without rendered title
}
func buildPostsQuery(c *postsRequestConfig, selection string) (query string, args []any, err error) {
queryBuilder := builderpool.Get()
defer builderpool.Put(queryBuilder)
// Selection
queryBuilder.WriteString("select ")
queryBuilder.WriteString(selection)
queryBuilder.WriteString(" from ")
// Table
if c.search != "" {
queryBuilder.WriteString("(select p.* from posts_fts(@search) ps, posts p where ps.path = p.path)")
args = append(args, sql.Named("search", c.search))
} else {
queryBuilder.WriteString("posts")
2020-11-15 10:34:48 +00:00
}
// Filter
queryBuilder.WriteString(" where 1")
2021-07-03 10:11:57 +00:00
if c.path != "" {
queryBuilder.WriteString(" and path = @path")
2021-07-03 10:11:57 +00:00
args = append(args, sql.Named("path", c.path))
}
if c.status != nil && len(c.status) > 0 {
queryBuilder.WriteString(" and status in (")
for i, status := range c.status {
if i > 0 {
queryBuilder.WriteString(", ")
}
named := "status" + strconv.Itoa(i)
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString("@")
queryBuilder.WriteString(named)
args = append(args, sql.Named(named, status))
}
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString(")")
}
if c.visibility != nil && len(c.visibility) > 0 {
queryBuilder.WriteString(" and visibility in (")
for i, visibility := range c.visibility {
if i > 0 {
queryBuilder.WriteString(", ")
}
named := "visibility" + strconv.Itoa(i)
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString("@")
queryBuilder.WriteString(named)
args = append(args, sql.Named(named, visibility))
}
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString(")")
}
if c.blog != "" {
queryBuilder.WriteString(" and blog = @blog")
args = append(args, sql.Named("blog", c.blog))
}
allParams := append(c.allParams, c.parameter)
allParamValues := append(c.allParamValues, c.parameterValue)
if len(allParams) > 0 {
if len(allParamValues) > 0 && len(allParamValues) != len(allParams) {
return "", nil, errors.New("number of parameters != number of parameter values")
}
for i, param := range allParams {
if param == "" {
continue
}
named := "allparam" + strconv.Itoa(i)
paramValue := allParamValues[i]
queryBuilder.WriteString(" and path in (select path from post_parameters where parameter = @")
queryBuilder.WriteString(named)
queryBuilder.WriteString(" and ")
if paramValue != "" {
namedVal := "allparamval" + strconv.Itoa(i)
queryBuilder.WriteString("value = @")
queryBuilder.WriteString(namedVal)
args = append(args, sql.Named(namedVal, paramValue))
} else {
queryBuilder.WriteString("length(coalesce(value, '')) > 0")
}
queryBuilder.WriteString(")")
args = append(args, sql.Named(named, param))
}
}
if len(c.anyParams) > 0 {
2021-11-13 19:19:46 +00:00
queryBuilder.WriteString(" and path in (select path from post_parameters where parameter in (")
for i, param := range c.anyParams {
2021-11-13 19:19:46 +00:00
if i > 0 {
queryBuilder.WriteString(", ")
}
named := "anyparam" + strconv.Itoa(i)
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString("@")
2021-11-13 19:19:46 +00:00
queryBuilder.WriteString(named)
args = append(args, param)
}
queryBuilder.WriteString(") and length(coalesce(value, '')) > 0)")
}
if c.excludeParameter != "" {
if c.excludeParameterValue != "" {
queryBuilder.WriteString(" and path not in (select path from post_parameters where parameter = @param and value = @paramval)")
args = append(args, sql.Named("param", c.excludeParameter), sql.Named("paramval", c.excludeParameterValue))
} else {
queryBuilder.WriteString(" and path not in (select path from post_parameters where parameter = @param and length(coalesce(value, '')) > 0)")
args = append(args, sql.Named("param", c.excludeParameter))
}
}
if c.taxonomy != nil && len(c.taxonomyValue) > 0 {
queryBuilder.WriteString(" and path in (select path from post_parameters where parameter = @taxname and lowerx(value) = lowerx(@taxval))")
args = append(args, sql.Named("taxname", c.taxonomy.Name), sql.Named("taxval", c.taxonomyValue))
}
if len(c.sections) > 0 {
queryBuilder.WriteString(" and section in (")
for i, section := range c.sections {
if i > 0 {
queryBuilder.WriteString(", ")
}
named := "section" + strconv.Itoa(i)
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString("@")
queryBuilder.WriteString(named)
args = append(args, sql.Named(named, section))
}
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString(")")
}
if c.publishedYear != 0 {
queryBuilder.WriteString(" and substr(tolocal(published), 1, 4) = @publishedyear")
args = append(args, sql.Named("publishedyear", fmt.Sprintf("%0004d", c.publishedYear)))
2020-12-13 14:16:47 +00:00
}
if c.publishedMonth != 0 {
queryBuilder.WriteString(" and substr(tolocal(published), 6, 2) = @publishedmonth")
args = append(args, sql.Named("publishedmonth", fmt.Sprintf("%02d", c.publishedMonth)))
2020-12-13 14:16:47 +00:00
}
if c.publishedDay != 0 {
queryBuilder.WriteString(" and substr(tolocal(published), 9, 2) = @publishedday")
args = append(args, sql.Named("publishedday", fmt.Sprintf("%02d", c.publishedDay)))
}
2021-12-11 18:43:40 +00:00
if !c.publishedBefore.IsZero() {
queryBuilder.WriteString(" and toutc(published) < @publishedbefore")
args = append(args, sql.Named("publishedbefore", c.publishedBefore.UTC().Format(time.RFC3339)))
}
// Order
queryBuilder.WriteString(" order by ")
if c.randomOrder {
queryBuilder.WriteString("random()")
} else if c.priorityOrder {
queryBuilder.WriteString("priority desc, published desc")
} else {
queryBuilder.WriteString("published desc")
}
// Limit & Offset
2021-07-03 10:11:57 +00:00
if c.limit != 0 || c.offset != 0 {
queryBuilder.WriteString(" limit @limit offset @offset")
args = append(args, sql.Named("limit", c.limit), sql.Named("offset", c.offset))
}
return queryBuilder.String(), args, nil
2021-07-03 10:11:57 +00:00
}
func (d *database) loadPostParameters(posts []*post, parameters ...string) (err error) {
if len(posts) == 0 {
return nil
}
// Build query
2022-03-16 07:28:03 +00:00
sqlArgs := make([]any, 0)
queryBuilder := builderpool.Get()
defer builderpool.Put(queryBuilder)
queryBuilder.WriteString("select path, parameter, value from post_parameters where")
// Paths
queryBuilder.WriteString(" path in (")
for i, p := range posts {
if i > 0 {
queryBuilder.WriteString(", ")
}
named := "path" + strconv.Itoa(i)
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString("@")
queryBuilder.WriteString(named)
sqlArgs = append(sqlArgs, sql.Named(named, p.Path))
}
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString(")")
// Parameters
2021-07-06 19:06:39 +00:00
if len(parameters) > 0 {
queryBuilder.WriteString(" and parameter in (")
2021-07-06 19:06:39 +00:00
for i, p := range parameters {
if i > 0 {
queryBuilder.WriteString(", ")
2021-07-06 19:06:39 +00:00
}
named := "param" + strconv.Itoa(i)
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString("@")
queryBuilder.WriteString(named)
2021-07-06 19:06:39 +00:00
sqlArgs = append(sqlArgs, sql.Named(named, p))
}
2023-01-24 13:30:53 +00:00
queryBuilder.WriteString(")")
}
// Order
queryBuilder.WriteString(" order by id")
2021-07-06 19:06:39 +00:00
// Query
rows, err := d.Query(queryBuilder.String(), sqlArgs...)
2021-07-03 10:11:57 +00:00
if err != nil {
return err
2021-07-03 10:11:57 +00:00
}
2021-07-06 19:06:39 +00:00
// Result
var path, name, value string
params := map[string]map[string][]string{}
2021-07-03 10:11:57 +00:00
for rows.Next() {
if err = rows.Scan(&path, &name, &value); err != nil {
return err
}
m, ok := params[path]
if !ok {
m = map[string][]string{}
2021-07-03 10:11:57 +00:00
}
m[name] = append(m[name], value)
params[path] = m
2021-07-03 10:11:57 +00:00
}
// Add to posts
for _, p := range posts {
p.Parameters = params[p.Path]
}
return nil
}
2021-08-05 06:09:34 +00:00
func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error) {
2021-07-02 14:52:09 +00:00
// Query posts
query, queryParams, err := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, visibility, priority")
if err != nil {
return nil, err
}
rows, err := a.db.Query(query, queryParams...)
if err != nil {
return nil, err
}
2021-07-03 10:11:57 +00:00
// Prepare row scanning
var path, content, published, updated, blog, section, status, visibility string
var priority int
for rows.Next() {
if err = rows.Scan(&path, &content, &published, &updated, &blog, &section, &status, &visibility, &priority); err != nil {
return nil, err
}
2021-07-03 10:11:57 +00:00
// Create new post, fill and add to list
p := &post{
Path: path,
Content: content,
Published: toLocalSafe(published),
Updated: toLocalSafe(updated),
Blog: blog,
Section: section,
Status: postStatus(status),
Visibility: postVisibility(visibility),
Priority: priority,
2021-07-03 10:11:57 +00:00
}
posts = append(posts, p)
2021-07-02 14:52:09 +00:00
}
if !config.fetchWithoutParams {
err = a.db.loadPostParameters(posts, config.fetchParams...)
if err != nil {
return nil, err
}
}
2021-08-05 06:09:34 +00:00
// Render post title
if !config.withoutRenderedTitle {
for _, p := range posts {
if t := p.Title(); t != "" {
p.RenderedTitle = a.renderMdTitle(t)
}
2021-08-05 06:09:34 +00:00
}
}
return posts, nil
}
2021-08-05 06:09:34 +00:00
func (a *goBlog) getPost(path string) (*post, error) {
posts, err := a.getPosts(&postsRequestConfig{path: path, limit: 1})
if err != nil {
return nil, err
} else if len(posts) == 0 {
return nil, errPostNotFound
}
return posts[0], nil
}
func (d *database) countPosts(config *postsRequestConfig) (count int, err error) {
query, params, err := buildPostsQuery(config, "path")
if err != nil {
return
}
row, err := d.QueryRow("select count(distinct path) from ("+query+")", params...)
if err != nil {
return
}
err = row.Scan(&count)
return
}
2021-07-03 10:11:57 +00:00
func (a *goBlog) getRandomPostPath(blog string) (path string, err error) {
2022-03-16 07:28:03 +00:00
sections := lo.Keys(a.cfg.Blogs[blog].Sections)
query, params, err := buildPostsQuery(&postsRequestConfig{
randomOrder: true,
limit: 1,
blog: blog,
sections: sections,
visibility: []postVisibility{visibilityPublic},
status: []postStatus{statusPublished},
}, "path")
if err != nil {
return
}
row, err := a.db.QueryRow(query, params...)
if err != nil {
2021-07-03 10:11:57 +00:00
return
}
err = row.Scan(&path)
if errors.Is(err, sql.ErrNoRows) {
return "", errPostNotFound
2021-07-03 10:11:57 +00:00
} else if err != nil {
return "", err
}
2021-07-03 10:11:57 +00:00
return path, nil
}
func (d *database) allTaxonomyValues(blog string, taxonomy string) ([]string, error) {
rows, err := d.Query(
"select distinct value from post_parameters where parameter = @tax and length(coalesce(value, '')) > 0 and path in (select path from posts where blog = @blog and status = @status and visibility = @visibility) order by value",
sql.Named("tax", taxonomy), sql.Named("blog", blog), sql.Named("status", statusPublished), sql.Named("visibility", visibilityPublic),
)
if err != nil {
return nil, err
}
2022-08-07 10:46:49 +00:00
var values []string
2021-07-03 10:11:57 +00:00
var value string
for rows.Next() {
2021-07-03 10:11:57 +00:00
if err = rows.Scan(&value); err != nil {
return nil, err
}
values = append(values, value)
}
return values, nil
}
2020-12-13 14:16:47 +00:00
2021-07-12 17:29:32 +00:00
const mediaUseSql = `
with mediafiles (name) as (values %s)
select name, count(path) as count from (
select distinct m.name, p.path
from mediafiles m, post_parameters p
where instr(p.value, m.name) > 0
union
select distinct m.name, p.path
from mediafiles m, posts_fts p
where p.content match '"' || m.name || '"'
)
group by name;
`
2022-01-27 17:17:44 +00:00
func (db *database) usesOfMediaFile(names ...string) (counts []int, err error) {
2022-03-16 07:28:03 +00:00
sqlArgs := []any{dbNoCache}
nameValues := builderpool.Get()
defer builderpool.Put(nameValues)
2021-07-12 17:29:32 +00:00
for i, n := range names {
if i > 0 {
nameValues.WriteString(", ")
2021-07-12 17:29:32 +00:00
}
named := "name" + strconv.Itoa(i)
nameValues.WriteString("(@")
nameValues.WriteString(named)
2023-01-24 13:30:53 +00:00
nameValues.WriteString(")")
2021-07-12 17:29:32 +00:00
sqlArgs = append(sqlArgs, sql.Named(named, n))
2021-06-29 16:11:42 +00:00
}
rows, err := db.Query(fmt.Sprintf(mediaUseSql, nameValues.String()), sqlArgs...)
2021-06-29 16:11:42 +00:00
if err != nil {
2021-07-12 17:29:32 +00:00
return nil, err
}
2022-01-27 17:17:44 +00:00
counts = make([]int, len(names))
2021-07-12 17:29:32 +00:00
var name string
var count int
for rows.Next() {
err = rows.Scan(&name, &count)
if err != nil {
return nil, err
}
2022-01-27 17:17:44 +00:00
for i, n := range names {
if n == name {
counts[i] = count
break
}
}
2021-06-29 16:11:42 +00:00
}
2021-07-12 17:29:32 +00:00
return counts, nil
2021-06-29 16:11:42 +00:00
}