2020-09-18 10:34:52 +00:00
package main
import (
2020-11-09 15:40:12 +00:00
"database/sql"
2020-09-18 10:34:52 +00:00
"errors"
2020-10-06 17:07:48 +00:00
"fmt"
2021-07-23 15:26:14 +00:00
"strconv"
2020-09-18 10:34:52 +00:00
"strings"
2020-10-06 17:07:48 +00:00
"text/template"
2020-09-18 10:34:52 +00:00
"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"
2022-02-22 15:52:03 +00:00
"go.goblog.app/app/pkgs/bufferpool"
2020-09-18 10:34:52 +00:00
)
2021-06-06 12:39:42 +00:00
func ( a * goBlog ) checkPost ( p * post ) ( err error ) {
2020-09-18 10:34:52 +00:00
if p == nil {
return errors . New ( "no post" )
}
2022-06-15 12:44:12 +00:00
now := time . Now ( ) . Local ( )
// 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
p . Section = a . cfg . Blogs [ p . Blog ] . DefaultSection
}
// Check section
if p . Section != "" {
if _ , ok := a . cfg . Blogs [ p . Blog ] . Sections [ p . Section ] ; ! ok {
return errors . New ( "section doesn't exist" )
}
}
// Maybe add published date
if p . Published == "" && p . Section != "" {
// Has no published date, but section -> published now
p . Published = now . Format ( time . RFC3339 )
}
// Fix and check date strings
2020-09-18 10:34:52 +00:00
if p . Published != "" {
2020-12-16 20:24:53 +00:00
p . Published , err = toLocal ( p . Published )
2020-09-18 10:34:52 +00:00
if err != nil {
return err
}
}
if p . Updated != "" {
2020-12-16 20:24:53 +00:00
p . Updated , err = toLocal ( p . Updated )
2020-09-18 10:34:52 +00:00
if err != nil {
return err
}
}
2022-06-15 12:44:12 +00:00
// 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 == "" {
p . Status = statusPublished
2022-01-03 13:23:25 +00:00
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
}
2022-06-15 12:44:12 +00:00
if publishedTime . After ( now ) {
2022-01-03 13:23:25 +00:00
p . Status = statusScheduled
}
}
2021-01-15 20:56:46 +00:00
}
2020-10-14 16:31:05 +00:00
// Cleanup params
2022-06-15 12:44:12 +00:00
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
}
2022-06-15 12:44:12 +00:00
p . Parameters [ pk ] = pvs
2020-12-12 22:44:03 +00:00
}
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 == "" {
2022-07-10 09:30:43 +00:00
published , parseErr := dateparse . ParseLocal ( p . Published )
if parseErr != nil {
published = now
2020-10-06 17:07:48 +00:00
}
if p . Slug == "" {
2022-06-15 12:44:12 +00:00
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 (
a . cfg . Blogs [ p . Blog ] . Sections [ p . Section ] . PathTemplate ,
"{{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" )
}
2022-02-23 20:33:02 +00:00
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" )
}
2020-09-18 10:34:52 +00:00
return nil
}
2021-06-06 12:39:42 +00:00
func ( a * goBlog ) createPost ( p * post ) error {
return a . createOrReplacePost ( p , & postCreationOptions { new : true } )
2021-01-15 20:56:46 +00:00
}
2021-06-06 12:39:42 +00:00
func ( a * goBlog ) replacePost ( p * post , oldPath string , oldStatus postStatus ) error {
return a . createOrReplacePost ( p , & postCreationOptions { new : false , oldPath : oldPath , oldStatus : oldStatus } )
2020-10-14 16:23:56 +00:00
}
2021-01-15 20:56:46 +00:00
type postCreationOptions struct {
new bool
oldPath string
oldStatus postStatus
2020-10-14 16:23:56 +00:00
}
2021-06-06 12:39:42 +00:00
func ( a * goBlog ) createOrReplacePost ( p * post , o * postCreationOptions ) error {
2021-06-15 15:36:41 +00:00
// Check post
if err := a . checkPost ( p ) ; err != nil {
return err
}
// Save to db
if err := a . db . savePost ( p , o ) ; err != nil {
2020-09-18 10:34:52 +00:00
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
}
2021-06-15 15:36:41 +00:00
// Trigger hooks
2021-07-14 14:15:24 +00:00
if p . Status == statusPublished || p . Status == statusUnlisted {
if o . new || ( o . oldStatus != statusPublished && o . oldStatus != statusUnlisted ) {
2021-06-15 15:36:41 +00:00
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
2021-06-15 15:36:41 +00:00
}
// Save check post to database
func ( db * database ) savePost ( p * post , o * postCreationOptions ) error {
2021-07-01 16:51:04 +00:00
// Check
if ! o . new && o . oldPath == "" {
return errors . New ( "old path required" )
}
// Lock post creation
2021-06-18 12:32:03 +00:00
db . pcm . Lock ( )
defer db . pcm . Unlock ( )
2021-05-29 11:32:00 +00:00
// Build SQL
2022-02-23 20:33:02 +00:00
sqlBuilder := bufferpool . Get ( )
defer bufferpool . Put ( sqlBuilder )
2022-03-16 07:28:03 +00:00
var sqlArgs = [ ] any { dbNoCache }
2021-07-01 16:51:04 +00:00
// Start transaction
sqlBuilder . WriteString ( "begin;" )
2022-04-16 19:42:09 +00:00
// Update or create post
if o . new {
// New post, create it
sqlBuilder . WriteString ( "insert into posts (path, content, published, updated, blog, section, status, priority) values (?, ?, ?, ?, ?, ?, ?, ?);" )
sqlArgs = append ( sqlArgs , p . Path , p . Content , toUTCSafe ( p . Published ) , toUTCSafe ( p . Updated ) , p . Blog , p . Section , p . Status , p . Priority )
} else {
// Delete post parameters
sqlBuilder . WriteString ( "delete from post_parameters where path = ?;" )
sqlArgs = append ( sqlArgs , o . oldPath )
2022-04-24 19:24:13 +00:00
// Update old post
sqlBuilder . WriteString ( "update posts set path = ?, content = ?, published = ?, updated = ?, blog = ?, section = ?, status = ?, priority = ? where path = ?;" )
sqlArgs = append ( sqlArgs , p . Path , p . Content , toUTCSafe ( p . Published ) , toUTCSafe ( p . Updated ) , p . Blog , p . Section , p . Status , p . Priority , o . oldPath )
2022-04-16 19:42:09 +00:00
}
2021-05-29 11:32:00 +00:00
// Insert post parameters
2020-09-18 10:34:52 +00:00
for param , value := range p . Parameters {
2022-04-24 19:24:13 +00:00
for _ , value := range lo . Filter ( value , loStringNotEmpty ) {
sqlBuilder . WriteString ( "insert into post_parameters (path, parameter, value) values (?, ?, ?);" )
sqlArgs = append ( sqlArgs , p . Path , param , value )
2020-09-18 10:34:52 +00:00
}
}
2021-07-01 16:51:04 +00:00
// Commit transaction
sqlBuilder . WriteString ( "commit;" )
2021-05-29 11:32:00 +00:00
// Execute
2022-08-09 15:25:22 +00:00
if _ , err := db . Exec ( sqlBuilder . String ( ) , sqlArgs ... ) ; err != nil {
2021-07-01 16:51:04 +00:00
if strings . Contains ( err . Error ( ) , "UNIQUE constraint failed: posts.path" ) {
return errors . New ( "post already exists at given path" )
}
2020-09-18 10:34:52 +00:00
return err
}
2021-06-15 15:36:41 +00:00
// Update FTS index
db . rebuildFTSIndex ( )
return nil
}
func ( a * goBlog ) deletePost ( path string ) error {
2022-01-03 12:55:44 +00:00
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 {
2021-06-15 15:36:41 +00:00
return err
2020-11-09 15:40:12 +00:00
}
2022-01-03 12:55:44 +00:00
// Post exists, check if it's already marked as deleted
if strings . HasSuffix ( string ( p . Status ) , statusDeletedSuffix ) {
// Post is already marked as deleted, delete it from database
2022-08-09 15:25:22 +00:00
if _ , err = a . db . Exec (
2022-04-24 19:24:13 +00:00
` begin; delete from posts where path = ?; insert or ignore into deleted (path) values (?); commit; ` ,
2022-01-03 12:55:44 +00:00
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 )
2022-01-03 12:55:44 +00:00
} else {
// Update post status
p . Status = postStatus ( string ( 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
2022-08-09 15:25:22 +00:00
if _ , err = a . db . Exec (
2022-04-24 19:24:13 +00:00
` 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; ` ,
2022-01-03 12:55:44 +00:00
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
2020-09-18 10:34:52 +00:00
}
2022-01-03 12:55:44 +00:00
func ( a * goBlog ) undeletePost ( path string ) error {
2020-10-14 16:23:56 +00:00
if path == "" {
2022-01-03 12:55:44 +00:00
return errors . New ( "path required" )
2020-09-18 10:34:52 +00:00
}
2022-01-03 12:55:44 +00:00
// Lock post creation
2021-08-05 06:09:34 +00:00
a . db . pcm . Lock ( )
defer a . db . pcm . Unlock ( )
2022-01-03 12:55:44 +00:00
// 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 {
2022-01-03 12:55:44 +00:00
return err
2020-11-08 22:04:32 +00:00
}
2022-01-03 12:55:44 +00:00
// Post exists, update status and parameters
p . Status = postStatus ( strings . TrimSuffix ( string ( p . Status ) , statusDeletedSuffix ) )
// Remove parameter
p . Parameters [ "deleted" ] = nil
// Update database
2022-08-09 15:25:22 +00:00
if _ , err = a . db . Exec (
2022-01-03 12:55:44 +00:00
` 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
}
2022-01-03 12:55:44 +00:00
// Rebuild FTS index
2021-08-05 06:09:34 +00:00
a . db . rebuildFTSIndex ( )
2022-01-03 12:55:44 +00:00
// Purge cache
a . cache . purge ( )
// Trigger hooks
a . postUndeleteHooks ( p )
return nil
2021-01-20 12:38:24 +00:00
}
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
2022-04-24 19:24:13 +00:00
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
2022-02-23 20:33:02 +00:00
sqlBuilder := bufferpool . Get ( )
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
2022-08-09 15:25:22 +00:00
_ , err := db . Exec ( sqlBuilder . String ( ) , sqlArgs ... )
2022-04-15 18:53:19 +00:00
bufferpool . Put ( sqlBuilder )
if err != nil {
2021-09-07 20:16:28 +00:00
return err
}
// Update FTS index
db . rebuildFTSIndex ( )
return nil
}
2020-11-09 15:40:12 +00:00
type postsRequestConfig struct {
2020-12-26 19:40:22 +00:00
search string
blog string
path string
limit int
offset int
sections [ ] string
2021-01-15 20:56:46 +00:00
status postStatus
2022-01-03 12:55:44 +00:00
statusse [ ] postStatus
2021-07-12 14:19:28 +00:00
taxonomy * configTaxonomy
2020-12-26 19:40:22 +00:00
taxonomyValue string
2021-11-13 19:19:46 +00:00
parameters [ ] string // Ignores parameterValue
parameter string // Ignores parameters
2020-12-26 19:40:22 +00:00
parameterValue string
2022-07-13 14:02:08 +00:00
excludeParameter string // exclude posts that have a certain parameter (with non-empty value)
excludeParameterValue string // ... with exactly this value
2020-12-26 19:40:22 +00:00
publishedYear , publishedMonth , publishedDay int
2021-12-11 18:43:40 +00:00
publishedBefore time . Time
2021-01-20 12:38:24 +00:00
randomOrder bool
2021-07-12 14:19:28 +00:00
priorityOrder bool
2021-07-03 10:11:57 +00:00
withoutParameters bool
2021-07-06 19:06:39 +00:00
withOnlyParameters [ ] string
2021-08-10 11:27:19 +00:00
withoutRenderedTitle bool
2020-11-09 15:40:12 +00:00
}
2022-03-16 07:28:03 +00:00
func buildPostsQuery ( c * postsRequestConfig , selection string ) ( query string , args [ ] any ) {
2022-02-23 20:33:02 +00:00
queryBuilder := bufferpool . Get ( )
defer bufferpool . Put ( queryBuilder )
2021-07-23 15:26:14 +00:00
// Selection
queryBuilder . WriteString ( "select " )
queryBuilder . WriteString ( selection )
queryBuilder . WriteString ( " from " )
// Table
2021-06-06 12:39:42 +00:00
if c . search != "" {
2021-11-14 07:40:19 +00:00
queryBuilder . WriteString ( "(select p.* from posts_fts(@search) ps, posts p where ps.path = p.path)" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "search" , c . search ) )
2021-07-23 15:26:14 +00:00
} else {
queryBuilder . WriteString ( "posts" )
2020-11-15 10:34:48 +00:00
}
2021-07-23 15:26:14 +00:00
// Filter
queryBuilder . WriteString ( " where 1" )
2021-07-03 10:11:57 +00:00
if c . path != "" {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and path = @path" )
2021-07-03 10:11:57 +00:00
args = append ( args , sql . Named ( "path" , c . path ) )
}
2021-06-06 12:39:42 +00:00
if c . status != "" && c . status != statusNil {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and status = @status" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "status" , c . status ) )
2021-01-15 20:56:46 +00:00
}
2022-01-03 12:55:44 +00:00
if c . statusse != nil && len ( c . statusse ) > 0 {
queryBuilder . WriteString ( " and status in (" )
for i , status := range c . statusse {
if i > 0 {
queryBuilder . WriteString ( ", " )
}
named := "status" + strconv . Itoa ( i )
queryBuilder . WriteByte ( '@' )
queryBuilder . WriteString ( named )
args = append ( args , sql . Named ( named , status ) )
}
queryBuilder . WriteByte ( ')' )
}
2021-06-06 12:39:42 +00:00
if c . blog != "" {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and blog = @blog" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "blog" , c . blog ) )
2020-11-09 15:40:12 +00:00
}
2021-06-06 12:39:42 +00:00
if c . parameter != "" {
if c . parameterValue != "" {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and path in (select path from post_parameters where parameter = @param and value = @paramval)" )
2021-07-03 10:11:57 +00:00
args = append ( args , sql . Named ( "param" , c . parameter ) , sql . Named ( "paramval" , c . parameterValue ) )
2020-11-09 15:40:12 +00:00
} else {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and path in (select path from post_parameters where parameter = @param and length(coalesce(value, '')) > 0)" )
2021-07-03 10:11:57 +00:00
args = append ( args , sql . Named ( "param" , c . parameter ) )
2020-11-09 15:40:12 +00:00
}
2021-11-13 19:19:46 +00:00
} else if len ( c . parameters ) > 0 {
queryBuilder . WriteString ( " and path in (select path from post_parameters where parameter in (" )
for i , param := range c . parameters {
if i > 0 {
queryBuilder . WriteString ( ", " )
}
named := "param" + strconv . Itoa ( i )
queryBuilder . WriteByte ( '@' )
queryBuilder . WriteString ( named )
args = append ( args , param )
}
queryBuilder . WriteString ( ") and length(coalesce(value, '')) > 0)" )
2020-11-09 15:40:12 +00:00
}
2022-07-13 14:02:08 +00:00
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 ) )
}
}
2021-06-06 12:39:42 +00:00
if c . taxonomy != nil && len ( c . taxonomyValue ) > 0 {
2021-08-09 11:09:45 +00:00
queryBuilder . WriteString ( " and path in (select path from post_parameters where parameter = @taxname and lowerx(value) = lowerx(@taxval))" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "taxname" , c . taxonomy . Name ) , sql . Named ( "taxval" , c . taxonomyValue ) )
2020-11-09 15:40:12 +00:00
}
2021-06-06 12:39:42 +00:00
if len ( c . sections ) > 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and section in (" )
2021-06-06 12:39:42 +00:00
for i , section := range c . sections {
2020-11-09 15:40:12 +00:00
if i > 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( ", " )
2020-11-09 15:40:12 +00:00
}
2021-07-23 15:26:14 +00:00
named := "section" + strconv . Itoa ( i )
queryBuilder . WriteByte ( '@' )
queryBuilder . WriteString ( named )
2020-11-09 15:40:12 +00:00
args = append ( args , sql . Named ( named , section ) )
}
2021-07-23 15:26:14 +00:00
queryBuilder . WriteByte ( ')' )
2020-11-09 15:40:12 +00:00
}
2021-06-06 12:39:42 +00:00
if c . publishedYear != 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and substr(tolocal(published), 1, 4) = @publishedyear" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "publishedyear" , fmt . Sprintf ( "%0004d" , c . publishedYear ) ) )
2020-12-13 14:16:47 +00:00
}
2021-06-06 12:39:42 +00:00
if c . publishedMonth != 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and substr(tolocal(published), 6, 2) = @publishedmonth" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "publishedmonth" , fmt . Sprintf ( "%02d" , c . publishedMonth ) ) )
2020-12-13 14:16:47 +00:00
}
2021-06-06 12:39:42 +00:00
if c . publishedDay != 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and substr(tolocal(published), 9, 2) = @publishedday" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "publishedday" , fmt . Sprintf ( "%02d" , c . publishedDay ) ) )
2020-12-26 19:40:22 +00:00
}
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 ) ) )
}
2021-07-23 15:26:14 +00:00
// Order
queryBuilder . WriteString ( " order by " )
2021-06-06 12:39:42 +00:00
if c . randomOrder {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( "random()" )
2021-07-12 14:19:28 +00:00
} else if c . priorityOrder {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( "priority desc, published desc" )
} else {
queryBuilder . WriteString ( "published desc" )
2021-01-20 12:38:24 +00:00
}
2021-07-23 15:26:14 +00:00
// Limit & Offset
2021-07-03 10:11:57 +00:00
if c . limit != 0 || c . offset != 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " limit @limit offset @offset" )
2021-06-06 12:39:42 +00:00
args = append ( args , sql . Named ( "limit" , c . limit ) , sql . Named ( "offset" , c . offset ) )
2020-11-09 15:40:12 +00:00
}
2021-07-23 15:26:14 +00:00
return queryBuilder . String ( ) , args
2021-07-03 10:11:57 +00:00
}
2021-07-19 14:32:45 +00:00
func ( d * database ) loadPostParameters ( posts [ ] * post , parameters ... string ) ( err error ) {
2021-07-23 15:26:14 +00:00
if len ( posts ) == 0 {
return nil
}
// Build query
2022-03-16 07:28:03 +00:00
sqlArgs := make ( [ ] any , 0 )
2022-02-23 20:33:02 +00:00
queryBuilder := bufferpool . Get ( )
defer bufferpool . Put ( queryBuilder )
2021-07-23 15:26:14 +00:00
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 )
queryBuilder . WriteByte ( '@' )
queryBuilder . WriteString ( named )
sqlArgs = append ( sqlArgs , sql . Named ( named , p . Path ) )
}
queryBuilder . WriteByte ( ')' )
// Parameters
2021-07-06 19:06:39 +00:00
if len ( parameters ) > 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( " and parameter in (" )
2021-07-06 19:06:39 +00:00
for i , p := range parameters {
if i > 0 {
2021-07-23 15:26:14 +00:00
queryBuilder . WriteString ( ", " )
2021-07-06 19:06:39 +00:00
}
2021-07-23 15:26:14 +00:00
named := "param" + strconv . Itoa ( i )
queryBuilder . WriteByte ( '@' )
queryBuilder . WriteString ( named )
2021-07-06 19:06:39 +00:00
sqlArgs = append ( sqlArgs , sql . Named ( named , p ) )
}
2021-07-23 15:26:14 +00:00
queryBuilder . WriteByte ( ')' )
2021-07-19 14:32:45 +00:00
}
2021-07-23 15:26:14 +00:00
// Order
queryBuilder . WriteString ( " order by id" )
2021-07-06 19:06:39 +00:00
// Query
2022-08-09 15:25:22 +00:00
rows , err := d . Query ( queryBuilder . String ( ) , sqlArgs ... )
2021-07-03 10:11:57 +00:00
if err != nil {
2021-07-19 14:32:45 +00:00
return err
2021-07-03 10:11:57 +00:00
}
2021-07-06 19:06:39 +00:00
// Result
2021-07-19 14:32:45 +00:00
var path , name , value string
params := map [ string ] map [ string ] [ ] string { }
2021-07-03 10:11:57 +00:00
for rows . Next ( ) {
2021-07-19 14:32:45 +00:00
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
}
2021-07-19 14:32:45 +00:00
m [ name ] = append ( m [ name ] , value )
params [ path ] = m
2021-07-03 10:11:57 +00:00
}
2021-07-19 14:32:45 +00:00
// Add to posts
for _ , p := range posts {
p . Parameters = params [ p . Path ]
}
return nil
2020-11-09 15:40:12 +00:00
}
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
2021-07-12 14:19:28 +00:00
query , queryParams := buildPostsQuery ( config , "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, priority" )
2022-08-09 15:25:22 +00:00
rows , err := a . db . Query ( query , queryParams ... )
2020-09-18 10:34:52 +00:00
if err != nil {
2020-11-09 15:40:12 +00:00
return nil , err
2020-09-18 10:34:52 +00:00
}
2021-07-03 10:11:57 +00:00
// Prepare row scanning
var path , content , published , updated , blog , section , status string
2021-07-12 14:19:28 +00:00
var priority int
2020-11-09 15:40:12 +00:00
for rows . Next ( ) {
2021-07-12 14:19:28 +00:00
if err = rows . Scan ( & path , & content , & published , & updated , & blog , & section , & status , & priority ) ; err != nil {
2020-11-09 15:40:12 +00:00
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 ) ,
2021-07-12 14:19:28 +00:00
Priority : priority ,
2021-07-03 10:11:57 +00:00
}
posts = append ( posts , p )
2021-07-02 14:52:09 +00:00
}
2021-07-19 14:32:45 +00:00
if ! config . withoutParameters {
2021-08-05 06:09:34 +00:00
err = a . db . loadPostParameters ( posts , config . withOnlyParameters ... )
2021-07-19 14:32:45 +00:00
if err != nil {
return nil , err
}
}
2021-08-05 06:09:34 +00:00
// Render post title
2021-08-10 11:27:19 +00:00
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
}
}
2020-11-09 15:40:12 +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 } )
2021-06-06 12:39:42 +00:00
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 ) {
2021-07-03 10:11:57 +00:00
query , params := buildPostsQuery ( config , "path" )
2022-08-09 15:25:22 +00:00
row , err := d . QueryRow ( "select count(distinct path) from (" + query + ")" , params ... )
2020-09-18 10:34:52 +00:00
if err != nil {
2020-11-09 15:40:12 +00:00
return
2020-09-18 10:34:52 +00:00
}
2020-11-09 15:40:12 +00:00
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 )
2021-07-03 10:11:57 +00:00
query , params := buildPostsQuery ( & postsRequestConfig { randomOrder : true , limit : 1 , blog : blog , sections : sections } , "path" )
2022-08-09 15:25:22 +00:00
row , err := a . db . QueryRow ( query , params ... )
2021-06-06 12:39:42 +00:00
if err != nil {
2021-07-03 10:11:57 +00:00
return
}
err = row . Scan ( & path )
if errors . Is ( err , sql . ErrNoRows ) {
2021-06-06 12:39:42 +00:00
return "" , errPostNotFound
2021-07-03 10:11:57 +00:00
} else if err != nil {
return "" , err
2021-06-06 12:39:42 +00:00
}
2021-07-03 10:11:57 +00:00
return path , nil
2021-06-06 12:39:42 +00:00
}
func ( d * database ) allTaxonomyValues ( blog string , taxonomy string ) ( [ ] string , error ) {
2022-01-03 12:55:44 +00:00
// TODO: Query posts the normal way
2022-08-09 15:25:22 +00:00
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) order by value" , sql . Named ( "tax" , taxonomy ) , sql . Named ( "blog" , blog ) , sql . Named ( "status" , statusPublished ) )
2020-11-09 15:40:12 +00:00
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
2020-11-09 15:40:12 +00:00
for rows . Next ( ) {
2021-07-03 10:11:57 +00:00
if err = rows . Scan ( & value ) ; err != nil {
return nil , err
}
2020-11-09 15:40:12 +00:00
values = append ( values , value )
}
return values , nil
2020-09-18 10:34:52 +00:00
}
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 }
2022-02-22 15:52:03 +00:00
nameValues := bufferpool . Get ( )
2021-07-12 17:29:32 +00:00
for i , n := range names {
if i > 0 {
2021-07-23 15:26:14 +00:00
nameValues . WriteString ( ", " )
2021-07-12 17:29:32 +00:00
}
2021-07-23 15:26:14 +00:00
named := "name" + strconv . Itoa ( i )
nameValues . WriteString ( "(@" )
nameValues . WriteString ( named )
nameValues . WriteByte ( ')' )
2021-07-12 17:29:32 +00:00
sqlArgs = append ( sqlArgs , sql . Named ( named , n ) )
2021-06-29 16:11:42 +00:00
}
2022-08-09 15:25:22 +00:00
rows , err := db . Query ( fmt . Sprintf ( mediaUseSql , nameValues . String ( ) ) , sqlArgs ... )
2022-02-22 15:52:03 +00:00
bufferpool . Put ( nameValues )
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
}