2020-07-28 19:17:07 +00:00
package main
import (
"errors"
2020-08-05 17:54:04 +00:00
"fmt"
2020-10-18 09:54:29 +00:00
"html/template"
2020-07-28 19:17:07 +00:00
"net/http"
2020-08-24 18:49:33 +00:00
"reflect"
2020-08-05 17:54:04 +00:00
"strconv"
2020-09-24 15:13:03 +00:00
"strings"
2020-10-06 17:07:48 +00:00
"github.com/go-chi/chi"
"github.com/vcraescu/go-paginator"
2020-07-28 19:17:07 +00:00
)
2020-07-30 19:18:13 +00:00
var errPostNotFound = errors . New ( "post not found" )
2020-07-28 19:17:07 +00:00
2020-10-15 15:32:46 +00:00
type post struct {
2020-08-31 19:12:43 +00:00
Path string ` json:"path" `
Content string ` json:"content" `
Published string ` json:"published" `
Updated string ` json:"updated" `
Parameters map [ string ] [ ] string ` json:"parameters" `
2020-10-06 17:07:48 +00:00
Blog string ` json:"blog" `
Section string ` json:"section" `
// Not persisted
2020-10-18 09:54:29 +00:00
Slug string ` json:"slug" `
rendered template . HTML
2020-07-28 19:17:07 +00:00
}
2020-07-29 14:41:36 +00:00
func servePost ( w http . ResponseWriter , r * http . Request ) {
2020-09-24 15:13:03 +00:00
if strings . HasSuffix ( r . URL . Path , ".as" ) {
servePostActivityStreams ( w , r )
return
}
2020-07-30 19:18:13 +00:00
path := slashTrimmedPath ( r )
2020-10-19 18:25:30 +00:00
p , err := getPost ( path )
2020-07-30 19:18:13 +00:00
if err == errPostNotFound {
2020-07-31 19:02:47 +00:00
serve404 ( w , r )
2020-07-29 14:41:36 +00:00
return
2020-07-28 19:17:07 +00:00
} else if err != nil {
2020-07-29 14:41:36 +00:00
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
2020-07-28 19:17:07 +00:00
}
2020-10-12 16:47:23 +00:00
render ( w , templatePost , & renderData {
2020-10-15 15:32:46 +00:00
blogString : p . Blog ,
Data : p ,
2020-10-12 16:47:23 +00:00
} )
2020-07-28 19:17:07 +00:00
}
2020-09-01 16:14:49 +00:00
type indexTemplateData struct {
2020-10-06 17:07:48 +00:00
Blog string
2020-09-01 16:14:49 +00:00
Title string
Description string
2020-10-15 15:32:46 +00:00
Posts [ ] * post
2020-09-01 16:14:49 +00:00
HasPrev bool
HasNext bool
2020-09-02 15:56:18 +00:00
First string
2020-09-01 16:14:49 +00:00
Prev string
Next string
2020-08-05 17:54:04 +00:00
}
2020-08-24 18:49:33 +00:00
type postPaginationAdapter struct {
2020-10-19 18:25:30 +00:00
config * postsRequestConfig
nums int
2020-08-24 18:49:33 +00:00
}
func ( p * postPaginationAdapter ) Nums ( ) int {
if p . nums == 0 {
2020-10-19 18:25:30 +00:00
p . nums , _ = countPosts ( p . config )
2020-08-24 18:49:33 +00:00
}
return p . nums
}
func ( p * postPaginationAdapter ) Slice ( offset , length int , data interface { } ) error {
if reflect . TypeOf ( data ) . Kind ( ) != reflect . Ptr {
panic ( "data has to be a pointer" )
}
2020-08-31 19:12:43 +00:00
modifiedConfig := * p . config
modifiedConfig . offset = offset
modifiedConfig . limit = length
2020-10-19 18:25:30 +00:00
posts , err := getPosts ( & modifiedConfig )
2020-08-24 18:49:33 +00:00
reflect . ValueOf ( data ) . Elem ( ) . Set ( reflect . ValueOf ( & posts ) . Elem ( ) )
return err
}
2020-10-15 17:50:34 +00:00
func serveHome ( blog string , path string ) func ( w http . ResponseWriter , r * http . Request ) {
2020-09-21 16:03:05 +00:00
return serveIndex ( & indexConfig {
2020-10-06 17:07:48 +00:00
blog : blog ,
2020-09-21 16:03:05 +00:00
path : path ,
} )
2020-08-25 18:55:32 +00:00
}
2020-10-15 17:50:34 +00:00
func serveSection ( blog string , path string , section * section ) func ( w http . ResponseWriter , r * http . Request ) {
2020-09-21 16:03:05 +00:00
return serveIndex ( & indexConfig {
2020-10-06 17:07:48 +00:00
blog : blog ,
2020-09-21 16:03:05 +00:00
path : path ,
section : section ,
} )
2020-08-31 19:12:43 +00:00
}
2020-10-06 17:07:48 +00:00
func serveTaxonomy ( blog string , tax * taxonomy ) func ( w http . ResponseWriter , r * http . Request ) {
2020-08-31 19:12:43 +00:00
return func ( w http . ResponseWriter , r * http . Request ) {
2020-10-06 17:07:48 +00:00
allValues , err := allTaxonomyValues ( blog , tax . Name )
2020-08-31 19:12:43 +00:00
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
2020-10-12 16:47:23 +00:00
render ( w , templateTaxonomy , & renderData {
blogString : blog ,
Data : struct {
Taxonomy * taxonomy
TaxonomyValues [ ] string
} {
Taxonomy : tax ,
TaxonomyValues : allValues ,
} ,
2020-08-31 19:12:43 +00:00
} )
}
2020-08-25 18:55:32 +00:00
}
2020-10-15 17:50:34 +00:00
func serveTaxonomyValue ( blog string , path string , tax * taxonomy , value string ) func ( w http . ResponseWriter , r * http . Request ) {
2020-09-21 16:03:05 +00:00
return serveIndex ( & indexConfig {
2020-10-06 17:07:48 +00:00
blog : blog ,
2020-09-21 16:03:05 +00:00
path : path ,
tax : tax ,
taxValue : value ,
} )
2020-08-31 19:12:43 +00:00
}
2020-10-06 17:07:48 +00:00
func servePhotos ( blog string ) func ( w http . ResponseWriter , r * http . Request ) {
2020-09-21 16:03:05 +00:00
return serveIndex ( & indexConfig {
2020-10-16 18:34:34 +00:00
blog : blog ,
path : appConfig . Blogs [ blog ] . Photos . Path ,
parameter : appConfig . Blogs [ blog ] . Photos . Parameter ,
template : templatePhotos ,
2020-09-21 16:03:05 +00:00
} )
}
type indexConfig struct {
2020-10-16 18:34:34 +00:00
blog string
path string
section * section
tax * taxonomy
taxValue string
parameter string
template string
2020-09-21 16:03:05 +00:00
}
func serveIndex ( ic * indexConfig ) func ( w http . ResponseWriter , r * http . Request ) {
2020-08-05 17:54:04 +00:00
return func ( w http . ResponseWriter , r * http . Request ) {
pageNoString := chi . URLParam ( r , "page" )
pageNo , _ := strconv . Atoi ( pageNoString )
2020-10-06 17:07:48 +00:00
var sections [ ] string
2020-09-21 16:03:05 +00:00
if ic . section != nil {
2020-10-06 17:07:48 +00:00
sections = [ ] string { ic . section . Name }
} else {
for sectionKey := range appConfig . Blogs [ ic . blog ] . Sections {
sections = append ( sections , sectionKey )
}
2020-08-25 18:55:32 +00:00
}
2020-10-19 18:25:30 +00:00
p := paginator . New ( & postPaginationAdapter { config : & postsRequestConfig {
2020-10-16 18:34:34 +00:00
blog : ic . blog ,
sections : sections ,
taxonomy : ic . tax ,
taxonomyValue : ic . taxValue ,
parameter : ic . parameter ,
2020-10-06 17:07:48 +00:00
} } , appConfig . Blogs [ ic . blog ] . Pagination )
2020-08-24 18:49:33 +00:00
p . SetPage ( pageNo )
2020-10-15 15:32:46 +00:00
var posts [ ] * post
2020-08-24 18:49:33 +00:00
err := p . Results ( & posts )
2020-08-05 17:54:04 +00:00
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
2020-09-14 14:07:37 +00:00
// Meta
2020-09-01 16:14:49 +00:00
var title , description string
2020-09-21 16:03:05 +00:00
if ic . tax != nil {
title = fmt . Sprintf ( "%s: %s" , ic . tax . Title , ic . taxValue )
} else if ic . section != nil {
title = ic . section . Title
description = ic . section . Description
2020-09-01 16:14:49 +00:00
}
2020-09-14 14:07:37 +00:00
// Check if feed
2020-10-15 17:50:34 +00:00
if ft := feedType ( chi . URLParam ( r , "feed" ) ) ; ft != noFeed {
generateFeed ( ic . blog , ft , w , r , posts , title , description )
2020-09-02 15:48:37 +00:00
return
}
2020-09-14 14:07:37 +00:00
// Navigation
prevPage , err := p . PrevPage ( )
if err == paginator . ErrNoPrevPage {
prevPage = p . Page ( )
}
nextPage , err := p . NextPage ( )
if err == paginator . ErrNoNextPage {
nextPage = p . Page ( )
}
2020-09-21 16:03:05 +00:00
template := ic . template
if len ( template ) == 0 {
template = templateIndex
}
2020-10-12 16:47:23 +00:00
render ( w , template , & renderData {
blogString : ic . blog ,
Data : & indexTemplateData {
Title : title ,
Description : description ,
Posts : posts ,
HasPrev : p . HasPrev ( ) ,
HasNext : p . HasNext ( ) ,
First : ic . path ,
Prev : fmt . Sprintf ( "%s/page/%d" , ic . path , prevPage ) ,
Next : fmt . Sprintf ( "%s/page/%d" , ic . path , nextPage ) ,
} ,
2020-08-05 17:54:04 +00:00
} )
2020-08-05 17:14:10 +00:00
}
}
2020-10-19 18:25:30 +00:00
func getPost ( path string ) ( * post , error ) {
posts , err := getPosts ( & postsRequestConfig { path : path } )
2020-07-29 16:28:51 +00:00
if err != nil {
return nil , err
2020-08-05 16:41:45 +00:00
} else if len ( posts ) == 0 {
return nil , errPostNotFound
2020-07-29 16:28:51 +00:00
}
2020-08-05 16:41:45 +00:00
return posts [ 0 ] , nil
2020-07-28 19:17:07 +00:00
}
2020-07-29 15:17:48 +00:00
2020-08-24 18:49:33 +00:00
type postsRequestConfig struct {
2020-10-16 18:34:34 +00:00
blog string
path string
limit int
offset int
sections [ ] string
taxonomy * taxonomy
taxonomyValue string
parameter string
parameterValue string
2020-08-05 16:41:45 +00:00
}
2020-10-17 18:22:00 +00:00
func buildQuery ( config * postsRequestConfig ) ( query string , params [ ] interface { } ) {
defaultSelection := "select p.path as path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), coalesce(blog, ''), coalesce(section, ''), coalesce(parameter, ''), coalesce(value, '') "
2020-08-25 18:55:32 +00:00
postsTable := "posts"
2020-10-06 17:07:48 +00:00
if config . blog != "" {
postsTable = "(select * from " + postsTable + " where blog = '" + config . blog + "')"
}
2020-10-16 18:34:34 +00:00
if config . parameter != "" {
if config . parameterValue != "" {
postsTable = "(select distinct p.* from " + postsTable + " p left outer join post_parameters pp on p.path = pp.path where pp.parameter = '" + config . parameter + "' and pp.value = '" + config . parameterValue + "')"
} else {
postsTable = "(select distinct p.* from " + postsTable + " p left outer join post_parameters pp on p.path = pp.path where pp.parameter = '" + config . parameter + "' and length(coalesce(pp.value, '')) > 1)"
}
2020-09-21 16:03:05 +00:00
}
2020-09-01 16:14:49 +00:00
if config . taxonomy != nil && len ( config . taxonomyValue ) > 0 {
2020-09-19 12:56:31 +00:00
postsTable = "(select distinct p.* from " + postsTable + " p left outer join post_parameters pp on p.path = pp.path where pp.parameter = '" + config . taxonomy . Name + "' and lower(pp.value) = lower('" + config . taxonomyValue + "'))"
2020-08-31 19:12:43 +00:00
}
if len ( config . sections ) > 0 {
postsTable = "(select * from " + postsTable + " where"
2020-08-25 18:55:32 +00:00
for i , section := range config . sections {
if i > 0 {
postsTable += " or"
}
2020-10-06 17:07:48 +00:00
postsTable += " section='" + section + "'"
2020-08-25 18:55:32 +00:00
}
postsTable += ")"
}
defaultTables := " from " + postsTable + " p left outer join post_parameters pp on p.path = pp.path "
2020-09-19 12:56:31 +00:00
defaultSorting := " order by p.published desc "
2020-08-24 18:49:33 +00:00
if config . path != "" {
2020-10-17 18:22:00 +00:00
query = defaultSelection + defaultTables + " where p.path=?" + defaultSorting
params = [ ] interface { } { config . path }
2020-08-24 18:49:33 +00:00
} else if config . limit != 0 || config . offset != 0 {
2020-10-17 18:22:00 +00:00
query = defaultSelection + " from (select * from " + postsTable + " p " + defaultSorting + " limit ? offset ?) p left outer join post_parameters pp on p.path = pp.path "
params = [ ] interface { } { config . limit , config . offset }
2020-08-05 16:41:45 +00:00
} else {
2020-10-17 18:22:00 +00:00
query = defaultSelection + defaultTables + defaultSorting
2020-08-05 16:41:45 +00:00
}
2020-10-17 18:22:00 +00:00
return
}
2020-10-19 18:25:30 +00:00
func getPosts ( config * postsRequestConfig ) ( posts [ ] * post , err error ) {
2020-10-17 18:22:00 +00:00
query , queryParams := buildQuery ( config )
2020-10-19 18:25:30 +00:00
rows , err := appDb . Query ( query , queryParams ... )
2020-07-29 16:28:51 +00:00
if err != nil {
2020-08-05 16:41:45 +00:00
return nil , err
2020-07-29 16:28:51 +00:00
}
2020-08-05 16:41:45 +00:00
defer func ( ) {
_ = rows . Close ( )
} ( )
2020-10-17 18:22:00 +00:00
paths := make ( map [ string ] int )
2020-07-29 16:28:51 +00:00
for rows . Next ( ) {
2020-10-15 15:32:46 +00:00
p := & post { }
2020-08-05 16:41:45 +00:00
var parameterName , parameterValue string
2020-10-15 15:32:46 +00:00
err = rows . Scan ( & p . Path , & p . Content , & p . Published , & p . Updated , & p . Blog , & p . Section , & parameterName , & parameterValue )
2020-08-05 16:41:45 +00:00
if err != nil {
return nil , err
}
2020-10-15 15:32:46 +00:00
if paths [ p . Path ] == 0 {
2020-08-05 16:41:45 +00:00
index := len ( posts )
2020-10-15 15:32:46 +00:00
paths [ p . Path ] = index + 1
p . Parameters = make ( map [ string ] [ ] string )
posts = append ( posts , p )
2020-08-05 16:41:45 +00:00
}
if parameterName != "" && posts != nil {
2020-10-15 15:32:46 +00:00
posts [ paths [ p . Path ] - 1 ] . Parameters [ parameterName ] = append ( posts [ paths [ p . Path ] - 1 ] . Parameters [ parameterName ] , parameterValue )
2020-08-05 16:41:45 +00:00
}
2020-07-29 16:28:51 +00:00
}
2020-08-05 16:41:45 +00:00
return posts , nil
2020-07-29 16:28:51 +00:00
}
2020-10-19 18:25:30 +00:00
func countPosts ( config * postsRequestConfig ) ( count int , err error ) {
2020-10-17 18:22:00 +00:00
query , params := buildQuery ( config )
query = "select count(distinct path) from (" + query + ")"
2020-10-19 18:25:30 +00:00
row := appDb . QueryRow ( query , params ... )
2020-10-17 18:22:00 +00:00
err = row . Scan ( & count )
return
2020-08-24 18:49:33 +00:00
}
2020-07-29 15:17:48 +00:00
func allPostPaths ( ) ( [ ] string , error ) {
var postPaths [ ] string
rows , err := appDb . Query ( "select path from posts" )
if err != nil {
return nil , err
}
for rows . Next ( ) {
var path string
_ = rows . Scan ( & path )
postPaths = append ( postPaths , path )
}
return postPaths , nil
}
2020-07-31 19:44:16 +00:00
2020-10-06 17:07:48 +00:00
func allTaxonomyValues ( blog string , taxonomy string ) ( [ ] string , error ) {
2020-08-31 19:12:43 +00:00
var values [ ] string
2020-10-06 17:07:48 +00:00
rows , err := appDb . Query ( "select distinct pp.value from posts p left outer join post_parameters pp on p.path = pp.path where pp.parameter = ? and length(coalesce(pp.value, '')) > 1 and blog = ?" , taxonomy , blog )
2020-08-31 19:12:43 +00:00
if err != nil {
return nil , err
}
for rows . Next ( ) {
var value string
_ = rows . Scan ( & value )
values = append ( values , value )
}
return values , nil
}