2020-07-28 19:17:07 +00:00
package main
import (
2020-07-29 15:17:48 +00:00
"context"
2020-07-28 19:17:07 +00:00
"database/sql"
"errors"
2020-08-05 17:54:04 +00:00
"fmt"
"github.com/go-chi/chi"
"github.com/vcraescu/go-paginator"
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-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-07-31 13:48:01 +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-07-28 19:17:07 +00:00
}
2020-07-29 14:41:36 +00:00
func servePost ( w http . ResponseWriter , r * http . Request ) {
2020-07-30 19:18:13 +00:00
path := slashTrimmedPath ( r )
post , err := getPost ( r . Context ( ) , path )
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-07-31 19:02:47 +00:00
render ( w , templatePost , post )
2020-07-28 19:17:07 +00:00
}
2020-09-01 16:14:49 +00:00
type indexTemplateData struct {
Title string
Description string
Posts [ ] * Post
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 {
context context . Context
2020-08-25 18:55:32 +00:00
config * postsRequestConfig
2020-08-24 18:49:33 +00:00
nums int
}
func ( p * postPaginationAdapter ) Nums ( ) int {
if p . nums == 0 {
2020-08-25 18:55:32 +00:00
p . nums , _ = countPosts ( p . context , 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
posts , err := getPosts ( p . context , & modifiedConfig )
2020-08-24 18:49:33 +00:00
reflect . ValueOf ( data ) . Elem ( ) . Set ( reflect . ValueOf ( & posts ) . Elem ( ) )
return err
}
2020-09-14 14:07:37 +00:00
func serveHome ( path string , ft feedType ) func ( w http . ResponseWriter , r * http . Request ) {
return serveIndex ( path , nil , nil , "" , ft )
2020-08-25 18:55:32 +00:00
}
2020-09-14 14:07:37 +00:00
func serveSection ( path string , section * section , ft feedType ) func ( w http . ResponseWriter , r * http . Request ) {
return serveIndex ( path , section , nil , "" , ft )
2020-08-31 19:12:43 +00:00
}
2020-09-01 16:14:49 +00:00
func serveTaxonomy ( 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-09-01 16:14:49 +00:00
allValues , err := allTaxonomyValues ( tax . Name )
2020-08-31 19:12:43 +00:00
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
render ( w , templateTaxonomy , struct {
2020-09-01 16:14:49 +00:00
Taxonomy * taxonomy
2020-08-31 19:12:43 +00:00
TaxonomyValues [ ] string
} {
2020-09-01 16:14:49 +00:00
Taxonomy : tax ,
2020-08-31 19:12:43 +00:00
TaxonomyValues : allValues ,
} )
}
2020-08-25 18:55:32 +00:00
}
2020-09-14 14:07:37 +00:00
func serveTaxonomyValue ( path string , tax * taxonomy , value string , ft feedType ) func ( w http . ResponseWriter , r * http . Request ) {
return serveIndex ( path , nil , tax , value , ft )
2020-08-31 19:12:43 +00:00
}
2020-09-14 14:07:37 +00:00
func serveIndex ( path string , sec * section , tax * taxonomy , taxonomyValue string , ft feedType ) 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-08-25 18:55:32 +00:00
sections := appConfig . Blog . Sections
2020-09-01 16:14:49 +00:00
if sec != nil {
sections = [ ] * section { sec }
2020-08-25 18:55:32 +00:00
}
2020-08-31 19:12:43 +00:00
p := paginator . New ( & postPaginationAdapter { context : r . Context ( ) , config : & postsRequestConfig {
sections : sections ,
2020-09-01 16:14:49 +00:00
taxonomy : tax ,
2020-08-31 19:12:43 +00:00
taxonomyValue : taxonomyValue ,
} } , appConfig . Blog . Pagination )
2020-08-24 18:49:33 +00:00
p . SetPage ( pageNo )
var posts [ ] * Post
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
if tax != nil {
title = fmt . Sprintf ( "%s: %s" , tax . Title , taxonomyValue )
} else if sec != nil {
title = sec . Title
description = sec . Description
}
2020-09-14 14:07:37 +00:00
// Check if feed
if ft != NONE {
2020-09-15 15:01:53 +00:00
generateFeed ( 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-01 16:14:49 +00:00
render ( w , templateIndex , & indexTemplateData {
Title : title ,
Description : description ,
Posts : posts ,
HasPrev : p . HasPrev ( ) ,
HasNext : p . HasNext ( ) ,
2020-09-02 15:56:18 +00:00
First : path ,
2020-09-01 16:14:49 +00:00
Prev : fmt . Sprintf ( "%s/page/%d" , path , prevPage ) ,
Next : fmt . Sprintf ( "%s/page/%d" , path , nextPage ) ,
2020-08-05 17:54:04 +00:00
} )
2020-08-05 17:14:10 +00:00
}
}
2020-07-31 13:48:01 +00:00
func getPost ( context context . Context , path string ) ( * Post , error ) {
2020-08-24 18:49:33 +00:00
posts , err := getPosts ( context , & 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-08-31 19:12:43 +00:00
path string
limit int
offset int
2020-09-01 16:14:49 +00:00
sections [ ] * section
taxonomy * taxonomy
2020-08-31 19:12:43 +00:00
taxonomyValue string
2020-08-05 16:41:45 +00:00
}
2020-08-24 18:49:33 +00:00
func getPosts ( context context . Context , config * postsRequestConfig ) ( posts [ ] * Post , err error ) {
2020-08-05 16:41:45 +00:00
paths := make ( map [ string ] int )
var rows * sql . Rows
2020-08-25 18:02:02 +00:00
defaultSelection := "select p.path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), coalesce(parameter, ''), coalesce(value, '') "
2020-08-25 18:55:32 +00:00
postsTable := "posts"
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-09-20 19:23:37 +00:00
postsTable += " section='" + section . Name + "'"
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-08-25 18:02:02 +00:00
query := defaultSelection + defaultTables + " where p.path=?" + defaultSorting
rows , err = appDb . QueryContext ( context , query , config . path )
2020-08-24 18:49:33 +00:00
} else if config . limit != 0 || config . offset != 0 {
2020-08-25 18:55:32 +00:00
query := defaultSelection + " from (select * from " + postsTable + " p " + defaultSorting + " limit ? offset ?) p left outer join post_parameters pp on p.path = pp.path "
2020-08-25 18:02:02 +00:00
rows , err = appDb . QueryContext ( context , query , config . limit , config . offset )
2020-08-05 16:41:45 +00:00
} else {
2020-08-25 18:02:02 +00:00
query := defaultSelection + defaultTables + defaultSorting
rows , err = appDb . QueryContext ( context , query )
2020-08-05 16:41:45 +00:00
}
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-07-29 16:28:51 +00:00
for rows . Next ( ) {
2020-08-05 16:41:45 +00:00
post := & Post { }
var parameterName , parameterValue string
err = rows . Scan ( & post . Path , & post . Content , & post . Published , & post . Updated , & parameterName , & parameterValue )
if err != nil {
return nil , err
}
if paths [ post . Path ] == 0 {
index := len ( posts )
paths [ post . Path ] = index + 1
2020-08-31 19:12:43 +00:00
post . Parameters = make ( map [ string ] [ ] string )
2020-08-05 16:41:45 +00:00
posts = append ( posts , post )
}
if parameterName != "" && posts != nil {
2020-08-31 19:12:43 +00:00
posts [ paths [ post . Path ] - 1 ] . Parameters [ parameterName ] = append ( posts [ paths [ post . 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-08-25 18:55:32 +00:00
func countPosts ( context context . Context , config * postsRequestConfig ) ( int , error ) {
posts , err := getPosts ( context , config )
return len ( posts ) , err
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-08-31 19:12:43 +00:00
func allTaxonomyValues ( taxonomy string ) ( [ ] string , error ) {
var values [ ] string
rows , err := appDb . Query ( "select distinct value from post_parameters where parameter = ? and value not null and value != ''" , taxonomy )
if err != nil {
return nil , err
}
for rows . Next ( ) {
var value string
_ = rows . Scan ( & value )
values = append ( values , value )
}
return values , nil
}