Browse Source

Front-end for blog

Vova Tkach 6 years ago
parent
commit
3c635e6fee

+ 3 - 0
Makefile

@@ -65,3 +65,6 @@ cy:
 
 ab:
 	ab -kc 10 -t 120 http://localhost:8080/
+	ab -kc 10 -t 120 http://localhost:8080/another/
+	ab -kc 10 -t 120 http://localhost:8080/not-existent-page/
+	ab -kc 10 -t 120 http://localhost:8080/blog/

+ 193 - 0
engine/fetdata/bak.blog.go

@@ -0,0 +1,193 @@
+package fetdata
+
+// import (
+// 	"math"
+// 	"strconv"
+// 	"strings"
+
+// 	"golang-fave/engine/sqlw"
+// 	"golang-fave/utils"
+// )
+
+// func (this *FERData) postsGetCount(buf string, cat int) (int, int) {
+// 	if cat == 0 {
+// 		var num int
+// 		if err := this.wrap.DB.QueryRow(`
+// 			SELECT
+// 				COUNT(*)
+// 			FROM
+// 				blog_posts
+// 			WHERE
+// 				active = 1
+// 			;
+// 		`).Scan(&num); err == nil {
+// 			pear_page := 2
+// 			max_pages := int(math.Ceil(float64(num) / float64(pear_page)))
+// 			curr_page := 1
+// 			p := this.wrap.R.URL.Query().Get("p")
+// 			if p != "" {
+// 				pi, err := strconv.Atoi(p)
+// 				if err != nil {
+// 					curr_page = 1
+// 				} else {
+// 					if pi < 1 {
+// 						curr_page = 1
+// 					} else if pi > max_pages {
+// 						curr_page = max_pages
+// 					} else {
+// 						curr_page = pi
+// 					}
+// 				}
+// 			}
+// 			limit_offset := curr_page*pear_page - pear_page
+// 			return limit_offset, pear_page
+// 		}
+// 	} else {
+// 		var num int
+// 		if err := this.wrap.DB.QueryRow(`
+// 			SELECT
+// 				COUNT(blog_posts.id)
+// 			FROM
+// 				blog_posts
+// 				LEFT JOIN blog_cat_post_rel ON blog_cat_post_rel.post_id = blog_posts.id
+// 			WHERE
+// 				blog_posts.active = 1 AND
+// 				blog_cat_post_rel.category_id = ?
+// 			;
+// 		`, cat).Scan(&num); err == nil {
+// 			pear_page := 2
+// 			max_pages := int(math.Ceil(float64(num) / float64(pear_page)))
+// 			curr_page := 1
+// 			p := this.wrap.R.URL.Query().Get("p")
+// 			if p != "" {
+// 				pi, err := strconv.Atoi(p)
+// 				if err != nil {
+// 					curr_page = 1
+// 				} else {
+// 					if pi < 1 {
+// 						curr_page = 1
+// 					} else if pi > max_pages {
+// 						curr_page = max_pages
+// 					} else {
+// 						curr_page = pi
+// 					}
+// 				}
+// 			}
+// 			limit_offset := curr_page*pear_page - pear_page
+// 			return limit_offset, pear_page
+// 		}
+// 	}
+// 	return 0, 0
+// }
+
+// func (this *FERData) postsToBuffer(buf string, cat int, order string) {
+// 	if this.bufferPosts == nil {
+// 		this.bufferPosts = map[string][]*BlogPost{}
+// 	}
+// 	if _, ok := this.bufferPosts[buf]; !ok {
+// 		var posts []*BlogPost
+
+// 		limit_offset, pear_page := this.postsGetCount(buf, cat)
+
+// 		var rows *sqlw.Rows
+// 		var err error
+
+// 		if cat == 0 {
+// 			rows, err = this.wrap.DB.Query(`
+// 				SELECT
+// 					blog_posts.id,
+// 					blog_posts.user,
+// 					blog_posts.name,
+// 					blog_posts.alias,
+// 					blog_posts.content,
+// 					UNIX_TIMESTAMP(blog_posts.datetime) AS datetime,
+// 					blog_posts.active
+// 				FROM
+// 					blog_posts
+// 				WHERE
+// 					blog_posts.active = 1
+// 				ORDER BY
+// 					blog_posts.id `+order+`
+// 				LIMIT ?, ?;
+// 			`, limit_offset, pear_page)
+// 		} else {
+// 			rows, err = this.wrap.DB.Query(`
+// 				SELECT
+// 					blog_posts.id,
+// 					blog_posts.user,
+// 					blog_posts.name,
+// 					blog_posts.alias,
+// 					blog_posts.content,
+// 					UNIX_TIMESTAMP(blog_posts.datetime) AS datetime,
+// 					blog_posts.active
+// 				FROM
+// 					blog_posts
+// 					LEFT JOIN blog_cat_post_rel ON blog_cat_post_rel.post_id = blog_posts.id
+// 				WHERE
+// 					blog_posts.active = 1 AND
+// 					blog_cat_post_rel.category_id = ?
+// 				ORDER BY
+// 					blog_posts.id `+order+`
+// 				LIMIT ?, ?;
+// 			`, cat, limit_offset, pear_page)
+// 		}
+
+// 		if err == nil {
+// 			var f_id int
+// 			var f_user int
+// 			var f_name string
+// 			var f_alias string
+// 			var f_content string
+// 			var f_datetime int
+// 			var f_active int
+// 			for rows.Next() {
+// 				err = rows.Scan(&f_id, &f_user, &f_name, &f_alias, &f_content, &f_datetime, &f_active)
+// 				if err == nil {
+// 					posts = append(posts, &BlogPost{
+// 						id:       f_id,
+// 						user:     f_user,
+// 						name:     f_name,
+// 						alias:    f_alias,
+// 						content:  f_content,
+// 						datetime: f_datetime,
+// 						active:   f_active,
+// 					})
+// 				}
+// 			}
+// 			rows.Close()
+// 		}
+// 		this.bufferPosts[buf] = posts
+// 	}
+// }
+
+// func (this *FERData) BlogPosts() []*BlogPost {
+// 	return this.BlogPostsOrder("DESC")
+// }
+
+// func (this *FERData) BlogPostsOrder(order string) []*BlogPost {
+// 	posts_order := "DESC"
+
+// 	if strings.ToLower(order) == "asc" {
+// 		posts_order = "ASC"
+// 	}
+
+// 	buf := "posts_" + posts_order
+// 	this.postsToBuffer(buf, 0, posts_order)
+// 	return this.bufferPosts[buf]
+// }
+
+// func (this *FERData) BlogPostsOfCat(cat int) []*BlogPost {
+// 	return this.BlogPostsOfCatOrder(cat, "DESC")
+// }
+
+// func (this *FERData) BlogPostsOfCatOrder(cat int, order string) []*BlogPost {
+// 	posts_order := "DESC"
+
+// 	if strings.ToLower(order) == "asc" {
+// 		posts_order = "ASC"
+// 	}
+
+// 	buf := "posts_" + posts_order + "_" + utils.IntToStr(cat)
+// 	this.postsToBuffer(buf, cat, posts_order)
+// 	return this.bufferPosts[buf]
+// }

+ 44 - 0
engine/fetdata/bak.blog_post.go

@@ -0,0 +1,44 @@
+package fetdata
+
+// import (
+// 	"html/template"
+// 	"time"
+// )
+
+// type BlogPost struct {
+// 	id       int
+// 	user     int
+// 	name     string
+// 	alias    string
+// 	content  string
+// 	datetime int
+// 	active   int
+// }
+
+// func (this *BlogPost) Id() int {
+// 	return this.id
+// }
+
+// func (this *BlogPost) Name() string {
+// 	return this.name
+// }
+
+// func (this *BlogPost) Alias() string {
+// 	return this.alias
+// }
+
+// func (this *BlogPost) Permalink() string {
+// 	return "/blog/" + this.alias + "/"
+// }
+
+// func (this *BlogPost) Content() template.HTML {
+// 	return template.HTML(this.content)
+// }
+
+// func (this *BlogPost) DateTime() int {
+// 	return this.datetime
+// }
+
+// func (this *BlogPost) DateTimeFormat(format string) string {
+// 	return time.Unix(int64(this.datetime), 0).Format(format)
+// }

+ 0 - 0
engine/fetdata/content.go → engine/fetdata/bak.content.go


+ 0 - 0
engine/fetdata/meta_data.go → engine/fetdata/bak.meta_data.go


+ 0 - 0
engine/fetdata/user.go → engine/fetdata/bak.user.go


+ 307 - 191
engine/fetdata/blog.go

@@ -1,193 +1,309 @@
 package fetdata
 
-// import (
-// 	"math"
-// 	"strconv"
-// 	"strings"
-
-// 	"golang-fave/engine/sqlw"
-// 	"golang-fave/utils"
-// )
-
-// func (this *FERData) postsGetCount(buf string, cat int) (int, int) {
-// 	if cat == 0 {
-// 		var num int
-// 		if err := this.wrap.DB.QueryRow(`
-// 			SELECT
-// 				COUNT(*)
-// 			FROM
-// 				blog_posts
-// 			WHERE
-// 				active = 1
-// 			;
-// 		`).Scan(&num); err == nil {
-// 			pear_page := 2
-// 			max_pages := int(math.Ceil(float64(num) / float64(pear_page)))
-// 			curr_page := 1
-// 			p := this.wrap.R.URL.Query().Get("p")
-// 			if p != "" {
-// 				pi, err := strconv.Atoi(p)
-// 				if err != nil {
-// 					curr_page = 1
-// 				} else {
-// 					if pi < 1 {
-// 						curr_page = 1
-// 					} else if pi > max_pages {
-// 						curr_page = max_pages
-// 					} else {
-// 						curr_page = pi
-// 					}
-// 				}
-// 			}
-// 			limit_offset := curr_page*pear_page - pear_page
-// 			return limit_offset, pear_page
-// 		}
-// 	} else {
-// 		var num int
-// 		if err := this.wrap.DB.QueryRow(`
-// 			SELECT
-// 				COUNT(blog_posts.id)
-// 			FROM
-// 				blog_posts
-// 				LEFT JOIN blog_cat_post_rel ON blog_cat_post_rel.post_id = blog_posts.id
-// 			WHERE
-// 				blog_posts.active = 1 AND
-// 				blog_cat_post_rel.category_id = ?
-// 			;
-// 		`, cat).Scan(&num); err == nil {
-// 			pear_page := 2
-// 			max_pages := int(math.Ceil(float64(num) / float64(pear_page)))
-// 			curr_page := 1
-// 			p := this.wrap.R.URL.Query().Get("p")
-// 			if p != "" {
-// 				pi, err := strconv.Atoi(p)
-// 				if err != nil {
-// 					curr_page = 1
-// 				} else {
-// 					if pi < 1 {
-// 						curr_page = 1
-// 					} else if pi > max_pages {
-// 						curr_page = max_pages
-// 					} else {
-// 						curr_page = pi
-// 					}
-// 				}
-// 			}
-// 			limit_offset := curr_page*pear_page - pear_page
-// 			return limit_offset, pear_page
-// 		}
-// 	}
-// 	return 0, 0
-// }
-
-// func (this *FERData) postsToBuffer(buf string, cat int, order string) {
-// 	if this.bufferPosts == nil {
-// 		this.bufferPosts = map[string][]*BlogPost{}
-// 	}
-// 	if _, ok := this.bufferPosts[buf]; !ok {
-// 		var posts []*BlogPost
-
-// 		limit_offset, pear_page := this.postsGetCount(buf, cat)
-
-// 		var rows *sqlw.Rows
-// 		var err error
-
-// 		if cat == 0 {
-// 			rows, err = this.wrap.DB.Query(`
-// 				SELECT
-// 					blog_posts.id,
-// 					blog_posts.user,
-// 					blog_posts.name,
-// 					blog_posts.alias,
-// 					blog_posts.content,
-// 					UNIX_TIMESTAMP(blog_posts.datetime) AS datetime,
-// 					blog_posts.active
-// 				FROM
-// 					blog_posts
-// 				WHERE
-// 					blog_posts.active = 1
-// 				ORDER BY
-// 					blog_posts.id `+order+`
-// 				LIMIT ?, ?;
-// 			`, limit_offset, pear_page)
-// 		} else {
-// 			rows, err = this.wrap.DB.Query(`
-// 				SELECT
-// 					blog_posts.id,
-// 					blog_posts.user,
-// 					blog_posts.name,
-// 					blog_posts.alias,
-// 					blog_posts.content,
-// 					UNIX_TIMESTAMP(blog_posts.datetime) AS datetime,
-// 					blog_posts.active
-// 				FROM
-// 					blog_posts
-// 					LEFT JOIN blog_cat_post_rel ON blog_cat_post_rel.post_id = blog_posts.id
-// 				WHERE
-// 					blog_posts.active = 1 AND
-// 					blog_cat_post_rel.category_id = ?
-// 				ORDER BY
-// 					blog_posts.id `+order+`
-// 				LIMIT ?, ?;
-// 			`, cat, limit_offset, pear_page)
-// 		}
-
-// 		if err == nil {
-// 			var f_id int
-// 			var f_user int
-// 			var f_name string
-// 			var f_alias string
-// 			var f_content string
-// 			var f_datetime int
-// 			var f_active int
-// 			for rows.Next() {
-// 				err = rows.Scan(&f_id, &f_user, &f_name, &f_alias, &f_content, &f_datetime, &f_active)
-// 				if err == nil {
-// 					posts = append(posts, &BlogPost{
-// 						id:       f_id,
-// 						user:     f_user,
-// 						name:     f_name,
-// 						alias:    f_alias,
-// 						content:  f_content,
-// 						datetime: f_datetime,
-// 						active:   f_active,
-// 					})
-// 				}
-// 			}
-// 			rows.Close()
-// 		}
-// 		this.bufferPosts[buf] = posts
-// 	}
-// }
-
-// func (this *FERData) BlogPosts() []*BlogPost {
-// 	return this.BlogPostsOrder("DESC")
-// }
-
-// func (this *FERData) BlogPostsOrder(order string) []*BlogPost {
-// 	posts_order := "DESC"
-
-// 	if strings.ToLower(order) == "asc" {
-// 		posts_order = "ASC"
-// 	}
-
-// 	buf := "posts_" + posts_order
-// 	this.postsToBuffer(buf, 0, posts_order)
-// 	return this.bufferPosts[buf]
-// }
-
-// func (this *FERData) BlogPostsOfCat(cat int) []*BlogPost {
-// 	return this.BlogPostsOfCatOrder(cat, "DESC")
-// }
-
-// func (this *FERData) BlogPostsOfCatOrder(cat int, order string) []*BlogPost {
-// 	posts_order := "DESC"
-
-// 	if strings.ToLower(order) == "asc" {
-// 		posts_order = "ASC"
-// 	}
-
-// 	buf := "posts_" + posts_order + "_" + utils.IntToStr(cat)
-// 	this.postsToBuffer(buf, cat, posts_order)
-// 	return this.bufferPosts[buf]
-// }
+import (
+	"math"
+	"strings"
+
+	"golang-fave/engine/wrapper"
+	"golang-fave/utils"
+)
+
+type BlogPagination struct {
+	Num     string
+	Link    string
+	Current bool
+}
+
+type Blog struct {
+	wrap     *wrapper.Wrapper
+	category *BlogCategory
+	post     *BlogPost
+
+	posts          []*BlogPost
+	postsCount     int
+	postsPerPage   int
+	postsMaxPage   int
+	postsCurrPage  int
+	pagination     []*BlogPagination
+	paginationPrev *BlogPagination
+	paginationNext *BlogPagination
+
+	bufferCats map[string][]*BlogCategory
+}
+
+func (this *Blog) init() {
+	if this == nil {
+		return
+	}
+	sql_nums := `
+		SELECT
+			COUNT(*)
+		FROM
+			blog_posts
+		WHERE
+			active = 1
+		;
+	`
+	sql_rows := `
+		SELECT
+			id,
+			user,
+			name,
+			alias,
+			content,
+			UNIX_TIMESTAMP(datetime) as datetime,
+			active
+		FROM
+			blog_posts
+		WHERE
+			active = 1
+		ORDER BY
+			id DESC
+		LIMIT ?, ?;
+	`
+
+	// Category selected
+	if this.category != nil {
+		var cat_ids []string
+		if rows, err := this.wrap.DB.Query(
+			`SELECT
+				node.id
+			FROM
+				blog_cats AS node,
+				blog_cats AS parent
+			WHERE
+				node.lft BETWEEN parent.lft AND parent.rgt AND
+				node.id > 1 AND
+				parent.id = ?
+			GROUP BY
+				node.id
+			ORDER BY
+				node.lft ASC
+			;`,
+			this.category.Id(),
+		); err == nil {
+			defer rows.Close()
+			for rows.Next() {
+				var cat_id string
+				if err := rows.Scan(&cat_id); err == nil {
+					cat_ids = append(cat_ids, cat_id)
+				}
+			}
+		}
+		sql_nums = `
+			SELECT
+				COUNT(*)
+			FROM
+				(
+					SELECT
+						COUNT(*)
+					FROM
+						blog_posts
+						LEFT JOIN blog_cat_post_rel ON blog_cat_post_rel.post_id = blog_posts.id
+					WHERE
+						blog_posts.active = 1 AND
+						blog_cat_post_rel.category_id IN (` + strings.Join(cat_ids, ", ") + `)
+					GROUP BY
+						blog_posts.id
+				) AS tbl
+			;
+		`
+		sql_rows = `
+			SELECT
+				blog_posts.id,
+				blog_posts.user,
+				blog_posts.name,
+				blog_posts.alias,
+				blog_posts.content,
+				UNIX_TIMESTAMP(blog_posts.datetime) AS datetime,
+				blog_posts.active
+			FROM
+				blog_posts
+				LEFT JOIN blog_cat_post_rel ON blog_cat_post_rel.post_id = blog_posts.id
+			WHERE
+				blog_posts.active = 1 AND
+				blog_cat_post_rel.category_id IN (` + strings.Join(cat_ids, ", ") + `)
+			GROUP BY
+				blog_posts.id
+			ORDER BY
+				blog_posts.id DESC
+			LIMIT ?, ?;
+		`
+	}
+
+	if err := this.wrap.DB.QueryRow(sql_nums).Scan(&this.postsCount); err == nil {
+		// TODO: to control panel settings
+		this.postsPerPage = 5
+		this.postsMaxPage = int(math.Ceil(float64(this.postsCount) / float64(this.postsPerPage)))
+		this.postsCurrPage = this.wrap.GetCurrentPage(this.postsMaxPage)
+		offset := this.postsCurrPage*this.postsPerPage - this.postsPerPage
+		if rows, err := this.wrap.DB.Query(sql_rows, offset, this.postsPerPage); err == nil {
+			defer rows.Close()
+			for rows.Next() {
+				row := utils.MySql_blog_posts{}
+				if err := rows.Scan(&row.A_id, &row.A_user, &row.A_name, &row.A_alias, &row.A_content, &row.A_datetime, &row.A_active); err == nil {
+					this.posts = append(this.posts, &BlogPost{object: &row})
+				}
+			}
+		}
+	}
+
+	// Build pagination
+	for i := 1; i <= this.postsMaxPage; i++ {
+		link := this.wrap.R.URL.Path
+		if i > 1 {
+			link = link + "?p=" + utils.IntToStr(i)
+		}
+		this.pagination = append(this.pagination, &BlogPagination{
+			Num:     utils.IntToStr(i),
+			Link:    link,
+			Current: i == this.postsCurrPage,
+		})
+	}
+
+	// Pagination prev/next
+	if this.postsMaxPage > 1 {
+		link := this.wrap.R.URL.Path
+		if this.postsCurrPage-1 > 1 {
+			link = this.wrap.R.URL.Path + "?p=" + utils.IntToStr(this.postsCurrPage-1)
+		}
+		this.paginationPrev = &BlogPagination{
+			Num:     utils.IntToStr(this.postsCurrPage - 1),
+			Link:    link,
+			Current: this.postsCurrPage <= 1,
+		}
+		if this.postsCurrPage >= 1 && this.postsCurrPage < this.postsMaxPage {
+			link = this.wrap.R.URL.Path + "?p=" + utils.IntToStr(this.postsCurrPage+1)
+		} else {
+			link = this.wrap.R.URL.Path + "?p=" + utils.IntToStr(this.postsMaxPage)
+		}
+		this.paginationNext = &BlogPagination{
+			Num:     utils.IntToStr(this.postsCurrPage + 1),
+			Link:    link,
+			Current: this.postsCurrPage >= this.postsMaxPage,
+		}
+	}
+}
+
+func (this *Blog) Category() *BlogCategory {
+	if this == nil {
+		return nil
+	}
+	return this.category
+}
+
+func (this *Blog) Post() *BlogPost {
+	if this == nil {
+		return nil
+	}
+	return this.post
+}
+
+func (this *Blog) HavePosts() bool {
+	if this == nil {
+		return false
+	}
+	if len(this.posts) <= 0 {
+		return false
+	}
+	return true
+}
+
+func (this *Blog) Posts() []*BlogPost {
+	if this == nil {
+		return []*BlogPost{}
+	}
+	return this.posts
+}
+
+func (this *Blog) PostsCount() int {
+	if this == nil {
+		return 0
+	}
+	return this.postsCount
+}
+
+func (this *Blog) PostsPerPage() int {
+	if this == nil {
+		return 0
+	}
+	return this.postsPerPage
+}
+
+func (this *Blog) PostsMaxPage() int {
+	if this == nil {
+		return 0
+	}
+	return this.postsMaxPage
+}
+
+func (this *Blog) PostsCurrPage() int {
+	if this == nil {
+		return 0
+	}
+	return this.postsCurrPage
+}
+
+func (this *Blog) Pagination() []*BlogPagination {
+	if this == nil {
+		return []*BlogPagination{}
+	}
+	return this.pagination
+}
+
+func (this *Blog) PaginationPrev() *BlogPagination {
+	if this == nil {
+		return nil
+	}
+	return this.paginationPrev
+}
+
+func (this *Blog) PaginationNext() *BlogPagination {
+	if this == nil {
+		return nil
+	}
+	return this.paginationNext
+}
+
+func (this *Blog) Categories() []*BlogCategory {
+	if this == nil {
+		return []*BlogCategory{}
+	}
+	if this.bufferCats == nil {
+		this.bufferCats = map[string][]*BlogCategory{}
+	}
+	key := ""
+	if _, ok := this.bufferCats[key]; !ok {
+		var cats []*BlogCategory
+		if rows, err := this.wrap.DB.Query(
+			`SELECT
+				node.id,
+				node.user,
+				node.name,
+				node.alias,
+				node.lft,
+				node.rgt
+			FROM
+				blog_cats AS node,
+				blog_cats AS parent
+			WHERE
+				node.lft BETWEEN parent.lft AND parent.rgt AND
+				node.id > 1
+			GROUP BY
+				node.id
+			ORDER BY
+				node.lft ASC
+			;`,
+		); err == nil {
+			defer rows.Close()
+			for rows.Next() {
+				row := utils.MySql_blog_category{}
+				if err := rows.Scan(&row.A_id, &row.A_user, &row.A_name, &row.A_alias, &row.A_lft, &row.A_rgt); err == nil {
+					cats = append(cats, &BlogCategory{object: &row})
+				}
+			}
+		}
+		this.bufferCats[key] = cats
+	}
+	return this.bufferCats[key]
+}

+ 58 - 0
engine/fetdata/blog_category.go

@@ -0,0 +1,58 @@
+package fetdata
+
+import (
+	"golang-fave/utils"
+)
+
+type BlogCategory struct {
+	object *utils.MySql_blog_category
+}
+
+func (this *BlogCategory) Id() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_id
+}
+
+func (this *BlogCategory) User() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_user
+}
+
+func (this *BlogCategory) Name() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_name
+}
+
+func (this *BlogCategory) Alias() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_alias
+}
+
+func (this *BlogCategory) Left() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_lft
+}
+
+func (this *BlogCategory) Right() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_rgt
+}
+
+func (this *BlogCategory) Permalink() string {
+	if this == nil {
+		return ""
+	}
+	return "/blog/category/" + this.object.A_alias + "/"
+}

+ 73 - 42
engine/fetdata/blog_post.go

@@ -1,44 +1,75 @@
 package fetdata
 
-// import (
-// 	"html/template"
-// 	"time"
-// )
-
-// type BlogPost struct {
-// 	id       int
-// 	user     int
-// 	name     string
-// 	alias    string
-// 	content  string
-// 	datetime int
-// 	active   int
-// }
-
-// func (this *BlogPost) Id() int {
-// 	return this.id
-// }
-
-// func (this *BlogPost) Name() string {
-// 	return this.name
-// }
-
-// func (this *BlogPost) Alias() string {
-// 	return this.alias
-// }
-
-// func (this *BlogPost) Permalink() string {
-// 	return "/blog/" + this.alias + "/"
-// }
-
-// func (this *BlogPost) Content() template.HTML {
-// 	return template.HTML(this.content)
-// }
-
-// func (this *BlogPost) DateTime() int {
-// 	return this.datetime
-// }
-
-// func (this *BlogPost) DateTimeFormat(format string) string {
-// 	return time.Unix(int64(this.datetime), 0).Format(format)
-// }
+import (
+	"html/template"
+	"time"
+
+	"golang-fave/utils"
+)
+
+type BlogPost struct {
+	object *utils.MySql_blog_posts
+}
+
+func (this *BlogPost) Id() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_id
+}
+
+func (this *BlogPost) User() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_user
+}
+
+func (this *BlogPost) Name() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_name
+}
+
+func (this *BlogPost) Alias() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_alias
+}
+
+func (this *BlogPost) Content() template.HTML {
+	if this == nil {
+		return template.HTML("")
+	}
+	return template.HTML(this.object.A_content)
+}
+
+func (this *BlogPost) DateTimeUnix() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_datetime
+}
+
+func (this *BlogPost) DateTimeFormat(format string) string {
+	if this == nil {
+		return ""
+	}
+	return time.Unix(int64(this.object.A_datetime), 0).Format(format)
+}
+
+func (this *BlogPost) Active() bool {
+	if this == nil {
+		return false
+	}
+	return this.object.A_active > 0
+}
+
+func (this *BlogPost) Permalink() string {
+	if this == nil {
+		return ""
+	}
+	return "/blog/" + this.object.A_alias + "/"
+}

+ 40 - 24
engine/fetdata/fetdata.go

@@ -8,32 +8,50 @@ import (
 )
 
 type FERData struct {
-	wrap    *wrapper.Wrapper
-	dataRow interface{}
-	is404   bool
+	wrap  *wrapper.Wrapper
+	is404 bool
 
-	bufferUser  *utils.MySql_user
-	bufferPosts map[string][]*BlogPost
+	Page *Page
+	Blog *Blog
 }
 
 func New(wrap *wrapper.Wrapper, drow interface{}, is404 bool) *FERData {
-	fer := &FERData{
-		wrap:    wrap,
-		dataRow: drow,
-		is404:   is404,
-	}
-	return fer.init()
-}
+	var d_Page *Page
+	var d_Blog *Blog
 
-func (this *FERData) init() *FERData {
-	if this.dataRow != nil {
-		if this.wrap.CurrModule == "index" {
-			if this.dataRow.(*utils.MySql_page).A_meta_title == "" {
-				this.dataRow.(*utils.MySql_page).A_meta_title = this.dataRow.(*utils.MySql_page).A_name
+	if wrap.CurrModule == "index" {
+		if o, ok := drow.(*utils.MySql_page); ok {
+			d_Page = &Page{object: o}
+		}
+	} else if wrap.CurrModule == "blog" {
+		if len(wrap.UrlArgs) == 3 && wrap.UrlArgs[0] == "blog" && wrap.UrlArgs[1] == "category" && wrap.UrlArgs[2] != "" {
+			if o, ok := drow.(*utils.MySql_blog_category); ok {
+				d_Blog = &Blog{wrap: wrap, category: &BlogCategory{object: o}}
+				d_Blog.init()
 			}
+		} else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "blog" && wrap.UrlArgs[1] != "" {
+			if o, ok := drow.(*utils.MySql_blog_posts); ok {
+				d_Blog = &Blog{wrap: wrap, post: &BlogPost{object: o}}
+				d_Blog.init()
+			}
+		} else {
+			d_Blog = &Blog{wrap: wrap}
+			d_Blog.init()
 		}
 	}
-	return this
+
+	if d_Blog == nil {
+		d_Blog = &Blog{wrap: wrap}
+	}
+
+	fer := &FERData{
+		wrap:  wrap,
+		is404: is404,
+		Page:  d_Page,
+		Blog:  d_Blog,
+	}
+
+	return fer
 }
 
 func (this *FERData) RequestURI() string {
@@ -52,27 +70,25 @@ func (this *FERData) Module() string {
 	if this.is404 {
 		return "404"
 	}
-
 	var mod string
 	if this.wrap.CurrModule == "index" {
 		mod = "index"
 	} else if this.wrap.CurrModule == "blog" {
-		if len(this.wrap.UrlArgs) >= 2 && this.wrap.UrlArgs[0] == "blog" && this.wrap.UrlArgs[1] == "category" {
+		if len(this.wrap.UrlArgs) == 3 && this.wrap.UrlArgs[0] == "blog" && this.wrap.UrlArgs[1] == "category" && this.wrap.UrlArgs[2] != "" {
 			mod = "blog-category"
-		} else if len(this.wrap.UrlArgs) >= 2 && this.wrap.UrlArgs[0] == "blog" && this.wrap.UrlArgs[1] != "" {
+		} else if len(this.wrap.UrlArgs) == 2 && this.wrap.UrlArgs[0] == "blog" && this.wrap.UrlArgs[1] != "" {
 			mod = "blog-post"
 		} else {
 			mod = "blog"
 		}
 	}
-
 	return mod
 }
 
-func (this *FERData) CurrentDateTime() int {
+func (this *FERData) DateTimeUnix() int {
 	return int(time.Now().Unix())
 }
 
-func (this *FERData) CurrentDateTimeFormat(format string) string {
+func (this *FERData) DateTimeFormat(format string) string {
 	return time.Unix(int64(time.Now().Unix()), 0).Format(format)
 }

+ 89 - 0
engine/fetdata/index.go

@@ -0,0 +1,89 @@
+package fetdata
+
+import (
+	"html/template"
+	"time"
+
+	"golang-fave/utils"
+)
+
+type Page struct {
+	object *utils.MySql_page
+}
+
+func (this *Page) Id() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_id
+}
+
+func (this *Page) User() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_user
+}
+
+func (this *Page) Name() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_name
+}
+
+func (this *Page) Alias() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_alias
+}
+
+func (this *Page) Content() template.HTML {
+	if this == nil {
+		return template.HTML("")
+	}
+	return template.HTML(this.object.A_content)
+}
+
+func (this *Page) MetaTitle() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_meta_title
+}
+
+func (this *Page) MetaKeywords() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_meta_keywords
+}
+
+func (this *Page) MetaDescription() string {
+	if this == nil {
+		return ""
+	}
+	return this.object.A_meta_description
+}
+
+func (this *Page) DateTimeUnix() int {
+	if this == nil {
+		return 0
+	}
+	return this.object.A_datetime
+}
+
+func (this *Page) DateTimeFormat(format string) string {
+	if this == nil {
+		return ""
+	}
+	return time.Unix(int64(this.object.A_datetime), 0).Format(format)
+}
+
+func (this *Page) Active() bool {
+	if this == nil {
+		return false
+	}
+	return this.object.A_active > 0
+}

+ 21 - 3
engine/wrapper/wrapper.go

@@ -7,6 +7,7 @@ import (
 	"html/template"
 	"net/http"
 	"os"
+	"strconv"
 	"time"
 
 	"golang-fave/consts"
@@ -203,9 +204,6 @@ func (this *Wrapper) RenderFrontEnd(tname string, data interface{}, status int)
 		utils.SystemErrorPageTemplate(this.W, err)
 		return
 	}
-	this.W.WriteHeader(status)
-	this.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
-	this.W.Header().Set("Content-Type", "text/html; charset=utf-8")
 	var tpl bytes.Buffer
 	err = tmpl.Execute(&tpl, consts.TmplData{
 		System: utils.GetTmplSystemData(),
@@ -215,6 +213,9 @@ func (this *Wrapper) RenderFrontEnd(tname string, data interface{}, status int)
 		utils.SystemErrorPageTemplate(this.W, err)
 		return
 	}
+	this.W.WriteHeader(status)
+	this.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
+	this.W.Header().Set("Content-Type", "text/html; charset=utf-8")
 	this.W.Write(tpl.Bytes())
 }
 
@@ -237,3 +238,20 @@ func (this *Wrapper) RenderBackEnd(tcont []byte, data interface{}) {
 	}
 	this.W.Write(tpl.Bytes())
 }
+
+func (this *Wrapper) GetCurrentPage(max int) int {
+	curr := 1
+	page := this.R.URL.Query().Get("p")
+	if page != "" {
+		if i, err := strconv.Atoi(page); err == nil {
+			if i < 1 {
+				curr = 1
+			} else if i > max {
+				curr = max
+			} else {
+				curr = i
+			}
+		}
+	}
+	return curr
+}

+ 42 - 13
hosts/localhost/template/blog-category.html

@@ -1,21 +1,50 @@
 {{template "header.html" .}}
 <div class="card mb-4">
-	{{range $.Data.BlogPostsOfCat $.Data.Id}}
-		<div class="post">
-			<div class="card-body">
-				<h2 class="card-title">
-					<a href="{{.Permalink}}">
-						{{.Name}}
-					</a>
-				</h2>
-				<div class="post-content">
-					{{.Content}}
-				</div>
-				<div class="post-date">
-					<small>Published on {{.DateTimeFormat "02/01/2006, 15:04:05"}}</small>
+	{{if $.Data.Blog.HavePosts}}
+		{{range $.Data.Blog.Posts}}
+			<div class="post">
+				<div class="card-body">
+					<h2 class="card-title">
+						<a href="{{.Permalink}}">
+							{{.Name}}
+						</a>
+					</h2>
+					<div class="post-content">
+						{{.Content}}
+					</div>
+					<div class="post-date">
+						<small>Published on {{.DateTimeFormat "02/01/2006, 15:04:05"}}</small>
+					</div>
 				</div>
 			</div>
+		{{end}}
+	{{else}}
+		<div class="card-body">
+			Sorry, no posts matched your criteria
 		</div>
 	{{end}}
 </div>
+{{if $.Data.Blog.HavePosts}}
+	{{if gt $.Data.Blog.PostsMaxPage 1 }}
+		<nav>
+			<ul class="pagination mb-4">
+				{{if $.Data.Blog.PaginationPrev}}
+					<li class="page-item{{if $.Data.Blog.PaginationPrev.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Blog.PaginationPrev.Link}}">Previous</a>
+					</li>
+				{{end}}
+				{{range $.Data.Blog.Pagination}}
+					<li class="page-item{{if .Current}} active{{end}}">
+						<a class="page-link" href="{{.Link}}">{{.Num}}</a>
+					</li>
+				{{end}}
+				{{if $.Data.Blog.PaginationNext}}
+					<li class="page-item{{if $.Data.Blog.PaginationNext.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Blog.PaginationNext.Link}}">Next</a>
+					</li>
+				{{end}}
+			</ul>
+		</nav>
+	{{end}}
+{{end}}
 {{template "footer.html" .}}

+ 3 - 3
hosts/localhost/template/blog-post.html

@@ -1,13 +1,13 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
-		<h2 class="card-title">{{$.Data.Name}}</h2>
+		<h2 class="card-title">{{$.Data.Blog.Post.Name}}</h2>
 		<div class="page-content">
-			{{$.Data.Content}}
+			{{$.Data.Blog.Post.Content}}
 		</div>
 	</div>
 	<div class="card-footer text-muted">
-		Published on {{$.Data.DateTimeFormat "02/01/2006, 15:04:05"}}
+		Published on {{$.Data.Blog.Post.DateTimeFormat "02/01/2006, 15:04:05"}}
 	</div>
 </div>
 {{template "footer.html" .}}

+ 42 - 13
hosts/localhost/template/blog.html

@@ -1,21 +1,50 @@
 {{template "header.html" .}}
 <div class="card mb-4">
-	{{range $.Data.BlogPosts}}
-		<div class="post">
-			<div class="card-body">
-				<h2 class="card-title">
-					<a href="{{.Permalink}}">
-						{{.Name}}
-					</a>
-				</h2>
-				<div class="post-content">
-					{{.Content}}
-				</div>
-				<div class="post-date">
-					<small>Published on {{.DateTimeFormat "02/01/2006, 15:04:05"}}</small>
+	{{if $.Data.Blog.HavePosts}}
+		{{range $.Data.Blog.Posts}}
+			<div class="post">
+				<div class="card-body">
+					<h2 class="card-title">
+						<a href="{{.Permalink}}">
+							{{.Name}}
+						</a>
+					</h2>
+					<div class="post-content">
+						{{.Content}}
+					</div>
+					<div class="post-date">
+						<small>Published on {{.DateTimeFormat "02/01/2006, 15:04:05"}}</small>
+					</div>
 				</div>
 			</div>
+		{{end}}
+	{{else}}
+		<div class="card-body">
+			Sorry, no posts matched your criteria
 		</div>
 	{{end}}
 </div>
+{{if $.Data.Blog.HavePosts}}
+	{{if gt $.Data.Blog.PostsMaxPage 1 }}
+		<nav>
+			<ul class="pagination mb-4">
+				{{if $.Data.Blog.PaginationPrev}}
+					<li class="page-item{{if $.Data.Blog.PaginationPrev.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Blog.PaginationPrev.Link}}">Previous</a>
+					</li>
+				{{end}}
+				{{range $.Data.Blog.Pagination}}
+					<li class="page-item{{if .Current}} active{{end}}">
+						<a class="page-link" href="{{.Link}}">{{.Num}}</a>
+					</li>
+				{{end}}
+				{{if $.Data.Blog.PaginationNext}}
+					<li class="page-item{{if $.Data.Blog.PaginationNext.Current}} disabled{{end}}">
+						<a class="page-link" href="{{$.Data.Blog.PaginationNext.Link}}">Next</a>
+					</li>
+				{{end}}
+			</ul>
+		</nav>
+	{{end}}
+{{end}}
 {{template "footer.html" .}}

+ 3 - 3
hosts/localhost/template/footer.html

@@ -9,10 +9,10 @@
 		<footer class="bg-light py-4">
 			<div class="container">
 				<p class="m-0 text-center text-black">
-					Copyright © Your Website {{if eq ($.Data.CurrentDateTimeFormat "2006") "2019"}}
-						{{$.Data.CurrentDateTimeFormat "2006"}}
+					Copyright © Your Website {{if eq ($.Data.DateTimeFormat "2006") "2019"}}
+						{{$.Data.DateTimeFormat "2006"}}
 					{{else}}
-						2019-{{$.Data.CurrentDateTimeFormat "2006"}}
+						2019-{{$.Data.DateTimeFormat "2006"}}
 					{{end}}
 				</p>
 			</div>

+ 9 - 9
hosts/localhost/template/header.html

@@ -12,12 +12,12 @@
 		<title>
 			{{if not (eq $.Data.Module "404")}}
 				{{if eq $.Data.Module "index"}}
-					{{$.Data.MetaTitle}}
+					{{$.Data.Page.Name}}
 				{{else if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}}
 					{{if eq $.Data.Module "blog-category"}}
-						Posts of category "{{$.Data.Name}}" | Blog
+						Posts of category "{{$.Data.Blog.Category.Name}}" | Blog
 					{{else if eq $.Data.Module "blog-post"}}
-						{{$.Data.Name}} | Blog
+						{{$.Data.Blog.Post.Name}} | Blog
 					{{else}}
 						Latest posts | Blog
 					{{end}}
@@ -26,8 +26,8 @@
 				Error 404
 			{{end}}
 		</title>
-		<meta name="keywords" content="{{$.Data.MetaKeywords}}" />
-		<meta name="description" content="{{$.Data.MetaDescription}}" />
+		<meta name="keywords" content="{{$.Data.Page.MetaKeywords}}" />
+		<meta name="description" content="{{$.Data.Page.MetaDescription}}" />
 		<link rel="shortcut icon" href="{{$.System.PathIcoFav}}" type="image/x-icon" />
 
 		<!-- Template CSS file from template folder -->
@@ -46,14 +46,14 @@
 					</button>
 					<div class="collapse navbar-collapse" id="navbarResponsive">
 						<ul class="navbar-nav ml-auto">
-							<li class="nav-item{{if eq $.Data.Alias "/"}} active{{end}}">
+							<li class="nav-item{{if eq $.Data.Page.Alias "/"}} active{{end}}">
 								<a class="nav-link" href="/">Home</a>
 							</li>
 							<li class="nav-item">
-								<a class="nav-link{{if eq $.Data.Alias "/another/"}} active{{end}}" href="/another/">Another</a>
+								<a class="nav-link{{if eq $.Data.Page.Alias "/another/"}} active{{end}}" href="/another/">Another</a>
 							</li>
 							<li class="nav-item">
-								<a class="nav-link{{if eq $.Data.Alias "/about/"}} active{{end}}" href="/about/">About</a>
+								<a class="nav-link{{if eq $.Data.Page.Alias "/about/"}} active{{end}}" href="/about/">About</a>
 							</li>
 							<li class="nav-item">
 								<a class="nav-link{{if eq $.Data.Module "404"}} active{{end}}" href="/not-existent-page/">404</a>
@@ -71,7 +71,7 @@
 						<h1 class="text-left text-white m-0 p-0 py-5">
 							{{if not (eq $.Data.Module "404")}}
 								{{if eq $.Data.Module "index"}}
-									{{if eq $.Data.Alias "/"}}
+									{{if eq $.Data.Page.Alias "/"}}
 										Welcome to home page
 									{{else}}
 										Welcome to some another page

+ 2 - 2
hosts/localhost/template/index.html

@@ -1,9 +1,9 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
-		<h2 class="card-title">{{$.Data.Name}}</h2>
+		<h2 class="card-title">{{$.Data.Page.Name}}</h2>
 		<div class="page-content">
-			{{$.Data.Content}}
+			{{$.Data.Page.Content}}
 		</div>
 	</div>
 	<div class="card-footer text-muted">

+ 3 - 3
hosts/localhost/template/page.html

@@ -1,13 +1,13 @@
 {{template "header.html" .}}
 <div class="card mb-4">
 	<div class="card-body">
-		<h2 class="card-title">{{$.Data.Name}}</h2>
+		<h2 class="card-title">{{$.Data.Page.Name}}</h2>
 		<div class="page-content">
-			{{$.Data.Content}}
+			{{$.Data.Page.Content}}
 		</div>
 	</div>
 	<div class="card-footer text-muted">
-		Published on {{$.Data.DateTimeFormat "02/01/2006, 15:04:05"}}
+		Published on {{$.Data.Page.DateTimeFormat "02/01/2006, 15:04:05"}}
 	</div>
 </div>
 {{template "footer.html" .}}

+ 12 - 0
hosts/localhost/template/sidebar-right.html

@@ -1,3 +1,15 @@
+<div class="card mb-4">
+	<h5 class="card-header">Categories</h5>
+	<div class="card-body">
+		<ul class="m-0 p-0 pl-4">
+			{{range $.Data.Blog.Categories}}
+				<li class="{{if and $.Data.Blog.Category (eq $.Data.Blog.Category.Id .Id)}}active{{end}}">
+					<a href="{{.Permalink}}">{{.Name}}</a>
+				</li>
+			{{end}}
+		</ul>
+	</div>
+</div>
 <div class="card mb-4">
 	<h5 class="card-header">Useful links</h5>
 	<div class="card-body">

+ 3 - 8
modules/module_index.go

@@ -68,19 +68,14 @@ func (this *Modules) RegisterModule_Index() *Module {
 			return
 		}
 
-		// Replace title with page name
-		if row.A_meta_title == "" {
-			row.A_meta_title = row.A_name
-		}
-
 		// Which template
-		tmpl_name := "index"
+		tname := "index"
 		if wrap.R.URL.Path != "/" {
-			tmpl_name = "page"
+			tname = "page"
 		}
 
 		// Render template
-		wrap.RenderFrontEnd(tmpl_name, fetdata.New(wrap, row, false), http.StatusOK)
+		wrap.RenderFrontEnd(tname, fetdata.New(wrap, row, false), http.StatusOK)
 	}, func(wrap *wrapper.Wrapper) (string, string, string) {
 		content := ""
 		sidebar := ""