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-31 19:44:16 +00:00
"strings"
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-07-31 19:44:16 +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-08-05 17:54:04 +00:00
type indexTemplateDate struct {
Posts [ ] * Post
HasPrev bool
HasNext bool
Prev string
Next string
}
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" )
}
posts , err := getPosts ( p . context , & postsRequestConfig {
2020-08-25 18:55:32 +00:00
sections : p . config . sections ,
offset : offset ,
limit : length ,
2020-08-24 18:49:33 +00:00
} )
reflect . ValueOf ( data ) . Elem ( ) . Set ( reflect . ValueOf ( & posts ) . Elem ( ) )
return err
}
2020-08-25 18:55:32 +00:00
func serveHome ( path string ) func ( w http . ResponseWriter , r * http . Request ) {
return serveIndex ( path , "" )
}
func serveSection ( path , section string ) func ( w http . ResponseWriter , r * http . Request ) {
return serveIndex ( path , section )
}
func serveIndex ( path string , section string ) 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
if len ( section ) > 0 {
sections = [ ] string { section }
}
p := paginator . New ( & postPaginationAdapter { context : r . Context ( ) , config : & postsRequestConfig { sections : sections } } , 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
}
prevPage , err := p . PrevPage ( )
if err == paginator . ErrNoPrevPage {
prevPage = p . Page ( )
}
nextPage , err := p . NextPage ( )
if err == paginator . ErrNoNextPage {
nextPage = p . Page ( )
}
render ( w , templateIndex , & indexTemplateDate {
Posts : posts ,
HasPrev : p . HasPrev ( ) ,
HasNext : p . HasNext ( ) ,
Prev : fmt . Sprintf ( "%s/page/%d" , path , prevPage ) ,
Next : fmt . Sprintf ( "%s/page/%d" , path , nextPage ) ,
} )
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-25 18:55:32 +00:00
path string
limit int
offset int
sections [ ] 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"
if len ( config . sections ) != 0 {
postsTable = "(select * from posts where"
for i , section := range config . sections {
if i > 0 {
postsTable += " or"
}
postsTable += " path like '/" + section + "/%'"
}
postsTable += ")"
}
defaultTables := " from " + postsTable + " p left outer join post_parameters pp on p.path = pp.path "
defaultSorting := " order by coalesce(p.updated, 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
post . Parameters = make ( map [ string ] string )
posts = append ( posts , post )
}
if parameterName != "" && posts != nil {
posts [ paths [ post . Path ] - 1 ] . Parameters [ parameterName ] = parameterValue
}
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-01 13:16:21 +00:00
func checkPost ( post * Post ) error {
2020-07-31 19:44:16 +00:00
if post == nil {
2020-08-01 13:16:21 +00:00
return errors . New ( "no post" )
2020-07-31 19:44:16 +00:00
}
if post . Path == "" || ! strings . HasPrefix ( post . Path , "/" ) {
return errors . New ( "wrong path" )
}
2020-08-01 13:16:21 +00:00
return nil
}
func createPost ( post * Post ) error {
err := checkPost ( post )
if err != nil {
return err
}
2020-07-31 19:44:16 +00:00
startWritingToDb ( )
tx , err := appDb . Begin ( )
if err != nil {
return err
}
_ , err = tx . Exec ( "insert into posts (path, content, published, updated) values (?, ?, ?, ?)" , post . Path , post . Content , post . Published , post . Updated )
if err != nil {
_ = tx . Rollback ( )
return err
}
for param , value := range post . Parameters {
_ , err = tx . Exec ( "insert into post_parameters (path, parameter, value) values (?, ?, ?)" , post . Path , param , value )
if err != nil {
_ = tx . Rollback ( )
return err
}
}
err = tx . Commit ( )
if err != nil {
return err
}
finishWritingToDb ( )
2020-08-01 13:16:21 +00:00
go purgeCache ( post . Path )
return reloadRouter ( )
}
func deletePost ( post * Post ) error {
err := checkPost ( post )
if err != nil {
return err
}
startWritingToDb ( )
tx , err := appDb . Begin ( )
if err != nil {
return err
}
_ , err = tx . Exec ( "delete from posts where path=?" , post . Path )
if err != nil {
_ = tx . Rollback ( )
return err
}
_ , err = tx . Exec ( "delete from post_parameters where path=?" , post . Path )
if err != nil {
_ = tx . Rollback ( )
return err
}
err = tx . Commit ( )
if err != nil {
return err
}
finishWritingToDb ( )
go purgeCache ( post . Path )
2020-07-31 19:44:16 +00:00
return reloadRouter ( )
}