Browse Source

Prepare for version 1.0.3

Cleanup, blog post short cotent, fix tests, block some aliases, rename mysql struct files
Vova Tkach 6 years ago
parent
commit
d9fe05fa13

+ 2 - 2
Dockerfile

@@ -2,8 +2,8 @@ FROM debian:latest
 
 ENV FAVE_HOST=0.0.0.0 FAVE_PORT=8080 FAVE_DIR=/app/hosts FAVE_DEBUG=false FAVE_KEEPALIVE=false
 
-ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.2/fave.linux-amd64.tar.gz /app/fave.linux-amd64.tar.gz
-ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.2/localhost.tar.gz /app/hosts/localhost.tar.gz
+ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.3/fave.linux-amd64.tar.gz /app/fave.linux-amd64.tar.gz
+ADD https://github.com/vladimirok5959/golang-fave/releases/download/v1.0.3/localhost.tar.gz /app/hosts/localhost.tar.gz
 
 RUN tar -zxf /app/fave.linux-amd64.tar.gz -C /app && \
  tar -zxf /app/hosts/localhost.tar.gz -C /app/hosts && \

+ 1 - 1
Makefile

@@ -1,4 +1,4 @@
-VERSION="1.0.2"
+VERSION="1.0.3"
 
 default: debug test run
 

+ 8 - 4
README.md

@@ -1,7 +1,5 @@
 # golang-fave
-CMS written on Go with MySQL as database. Dynamical, splitted by modules, user friendly and thanks bootstrap is fully adaptive for mobile devices and tablets. Thanks Go language it's fastern, all in one binary file, no need to install additional web servers. Go native template with vars allow to do almost all what are need.
-
-![](support/screenshots-1.gif)
+CMS written on Go with MySQL as database. Dynamical, splitted by modules, user friendly and thanks bootstrap is fully adaptive for mobile devices and tablets. All in one binary file, no need to install additional web servers. Go native templates with vars allow to do almost all what are need.
 
 ## Usage
 ```
@@ -30,9 +28,12 @@ hosts
 ├────── error.log          # Error log file
 ├──── template             # Engine templates
 ├────── 404.html           # Template for 404 page
+├────── blog-category.html # Template for blog category
+├────── blog-post.html     # Template for blog post
+├────── blog.html          # Template for blog home page
 ├────── footer.html        # Footer
 ├────── header.html        # Header
-├────── index.html         # Template for index page
+├────── index.html         # Template for home page
 ├────── page.html          # Template for any other pages
 ├────── robots.txt         # Host robots.txt file
 ├────── scripts.js         # Theme scripts file
@@ -50,3 +51,6 @@ Unlimited hosts count. Template variables in [Wiki](https://github.com/vladimiro
 * **make update** - get all dependencies and put to vendor folder
 * **make docker-test** - build image and start on port 8080
 * **make docker-img** - build docker image only
+* **make cy-dev** - cypress tests in browser
+* **make cy** - cypress tests in console
+* **make ab** - http stress test

+ 6 - 2
assets/cp.styles.css

@@ -348,6 +348,10 @@ ul.pagination {
 	min-height: 5.4rem;
 }
 
+.data-form textarea.briefly {
+	min-height: 3.9rem;
+}
+
 /* Checkbox style iOS */
 .checkbox-ios {
 	display: inline-block;
@@ -438,8 +442,8 @@ ul.pagination {
 		margin-bottom: 0px;
 	}
 
-	.data-form.blog-add .form-group.n6,
-	.data-form.blog-modify .form-group.n6 {
+	.data-form.blog-add .form-group.n7,
+	.data-form.blog-modify .form-group.n7 {
 		margin-bottom: 0px;
 	}
 

File diff suppressed because it is too large
+ 0 - 0
assets/cp.styles.css.go


+ 2 - 2
consts/consts.go

@@ -4,8 +4,8 @@ import (
 	"html/template"
 )
 
-const ServerVersion = "1.0.2"
-const AssetsVersion = "18"
+const ServerVersion = "1.0.3"
+const AssetsVersion = "19"
 const AssetsPath = "assets"
 const DirIndexFile = "index.html"
 

+ 3 - 1
cypress/integration/control-panel/002_module_blog/001_posts.js

@@ -20,7 +20,7 @@ context('Module blog posts', () => {
     cy.visitCMS('/cp/blog/add/');
     cy.get('.data-form.blog-add input[type=text]').should('have.length', 2);
     cy.get('.data-form.blog-add select').should('have.length', 1);
-    cy.get('.data-form.blog-add textarea').should('have.length', 1);
+    cy.get('.data-form.blog-add textarea').should('have.length', 2);
     cy.get('.data-form.blog-add input[type=checkbox]').should('have.length', 1);
     cy.logoutCMS();
   });
@@ -39,6 +39,7 @@ context('Module blog posts', () => {
     cy.visitCMS('/cp/blog/add/');
     cy.get('.data-form.blog-add input[name=name]').clear().type('Some test post');
     cy.get('.data-form.blog-add select#lbl_cats').select(['Health and food', '— — Natural']).invoke('val').should('deep.equal', ['2', '7']);
+    cy.get('.data-form.blog-add textarea[name=briefly]').clear().type('Some brief content');
     cy.get('.data-form.blog-add textarea[name=content]').clear().type('Some test content');
     cy.get('.data-form.blog-add label[for=lbl_active]').click();
     cy.get('#add-edit-button').click();
@@ -62,6 +63,7 @@ context('Module blog posts', () => {
     cy.get('.data-form.blog-modify input[name=name]').should('have.value', 'Some test post');
     cy.get('.data-form.blog-modify input[name=alias]').should('have.value', 'some-test-post');
     cy.get('.data-form.blog-modify select#lbl_cats').invoke('val').should('deep.equal', ['2', '7']);
+    cy.get('.data-form.blog-modify textarea[name=briefly]').should('have.value', 'Some brief content');
     cy.get('.data-form.blog-modify textarea[name=content]').should('have.value', 'Some test content');
     cy.get('.data-form.blog-modify input[name=active]').should('be.checked');
     cy.logoutCMS();

+ 38 - 23
engine/fetdata/blog.go

@@ -50,6 +50,7 @@ func (this *Blog) init() {
 			user,
 			name,
 			alias,
+			briefly,
 			content,
 			UNIX_TIMESTAMP(datetime) as datetime,
 			active
@@ -114,6 +115,7 @@ func (this *Blog) init() {
 				blog_posts.user,
 				blog_posts.name,
 				blog_posts.alias,
+				blog_posts.briefly,
 				blog_posts.content,
 				UNIX_TIMESTAMP(blog_posts.datetime) AS datetime,
 				blog_posts.active
@@ -140,8 +142,8 @@ func (this *Blog) init() {
 		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 {
+				row := utils.MySql_blog_post{}
+				if err := rows.Scan(&row.A_id, &row.A_user, &row.A_name, &row.A_alias, &row.A_briefly, &row.A_content, &row.A_datetime, &row.A_active); err == nil {
 					this.posts = append(this.posts, &BlogPost{object: &row})
 				}
 			}
@@ -265,7 +267,7 @@ func (this *Blog) PaginationNext() *BlogPagination {
 	return this.paginationNext
 }
 
-func (this *Blog) Categories() []*BlogCategory {
+func (this *Blog) Categories(mlvl int) []*BlogCategory {
 	if this == nil {
 		return []*BlogCategory{}
 	}
@@ -273,33 +275,46 @@ func (this *Blog) Categories() []*BlogCategory {
 		this.bufferCats = map[string][]*BlogCategory{}
 	}
 	key := ""
+	where := ``
+	if mlvl > 0 {
+		where += `AND tbl.depth <= ` + utils.IntToStr(mlvl)
+	}
 	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
+		if rows, err := this.wrap.DB.Query(`
+			SELECT
+				tbl.*
 			FROM
-				blog_cats AS node,
-				blog_cats AS parent
+				(
+					SELECT
+						node.id,
+						node.user,
+						node.name,
+						node.alias,
+						node.lft,
+						node.rgt,
+						(COUNT(parent.id) - 1) AS depth
+					FROM
+						blog_cats AS node,
+						blog_cats AS parent
+					WHERE
+						node.lft BETWEEN parent.lft AND parent.rgt
+					GROUP BY
+						node.id
+					ORDER BY
+						node.lft ASC
+				) AS tbl
 			WHERE
-				node.lft BETWEEN parent.lft AND parent.rgt AND
-				node.id > 1
-			GROUP BY
-				node.id
-			ORDER BY
-				node.lft ASC
-			;`,
-		); err == nil {
+				tbl.id > 1
+				` + where + `
+			;
+		`); 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})
+				var Depth int
+				if err := rows.Scan(&row.A_id, &row.A_user, &row.A_name, &row.A_alias, &row.A_lft, &row.A_rgt, &Depth); err == nil {
+					cats = append(cats, &BlogCategory{object: &row, depth: Depth})
 				}
 			}
 		}

+ 8 - 0
engine/fetdata/blog_category.go

@@ -6,6 +6,7 @@ import (
 
 type BlogCategory struct {
 	object *utils.MySql_blog_category
+	depth  int
 }
 
 func (this *BlogCategory) Id() int {
@@ -56,3 +57,10 @@ func (this *BlogCategory) Permalink() string {
 	}
 	return "/blog/category/" + this.object.A_alias + "/"
 }
+
+func (this *BlogCategory) Level() int {
+	if this == nil {
+		return 0
+	}
+	return this.depth
+}

+ 8 - 1
engine/fetdata/blog_post.go

@@ -8,7 +8,7 @@ import (
 )
 
 type BlogPost struct {
-	object *utils.MySql_blog_posts
+	object *utils.MySql_blog_post
 }
 
 func (this *BlogPost) Id() int {
@@ -39,6 +39,13 @@ func (this *BlogPost) Alias() string {
 	return this.object.A_alias
 }
 
+func (this *BlogPost) Briefly() template.HTML {
+	if this == nil {
+		return template.HTML("")
+	}
+	return template.HTML(this.object.A_briefly)
+}
+
 func (this *BlogPost) Content() template.HTML {
 	if this == nil {
 		return template.HTML("")

+ 1 - 1
engine/fetdata/fetdata.go

@@ -30,7 +30,7 @@ func New(wrap *wrapper.Wrapper, drow interface{}, is404 bool) *FERData {
 				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 {
+			if o, ok := drow.(*utils.MySql_blog_post); ok {
 				d_Blog = &Blog{wrap: wrap, post: &BlogPost{object: o}}
 				d_Blog.init()
 			}

+ 22 - 1
engine/wrapper/wrapper.go

@@ -193,7 +193,28 @@ func (this *Wrapper) RenderToString(tcont []byte, data interface{}) string {
 }
 
 func (this *Wrapper) RenderFrontEnd(tname string, data interface{}, status int) {
-	tmpl, err := template.ParseFiles(
+	tmplFuncs := template.FuncMap{
+		"plus": func(a, b int) int {
+			return a + b
+		},
+		"minus": func(a, b int) int {
+			return a - b
+		},
+		"multiply": func(a, b int) int {
+			return a * b
+		},
+		"divide": func(a, b int) int {
+			return a / b
+		},
+		"repeat": func(a string, n int) template.HTML {
+			out := ""
+			for i := 1; i <= n; i++ {
+				out += a
+			}
+			return template.HTML(out)
+		},
+	}
+	tmpl, err := template.New(tname+".html").Funcs(tmplFuncs).ParseFiles(
 		this.DTemplate+string(os.PathSeparator)+tname+".html",
 		this.DTemplate+string(os.PathSeparator)+"header.html",
 		this.DTemplate+string(os.PathSeparator)+"sidebar-left.html",

+ 1 - 1
hosts/localhost/template/blog-category.html

@@ -10,7 +10,7 @@
 						</a>
 					</h2>
 					<div class="post-content">
-						{{.Content}}
+						{{.Briefly}}
 					</div>
 					<div class="post-date">
 						<small>Published on {{.DateTimeFormat "02/01/2006, 15:04:05"}}</small>

+ 1 - 1
hosts/localhost/template/blog.html

@@ -10,7 +10,7 @@
 						</a>
 					</h2>
 					<div class="post-content">
-						{{.Content}}
+						{{.Briefly}}
 					</div>
 					<div class="post-date">
 						<small>Published on {{.DateTimeFormat "02/01/2006, 15:04:05"}}</small>

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

@@ -56,10 +56,10 @@
 								<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>
+								<a class="nav-link{{if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}} active{{end}}" href="/blog/">Blog</a>
 							</li>
 							<li class="nav-item">
-								<a class="nav-link{{if or (eq $.Data.Module "blog") (eq $.Data.Module "blog-post") (eq $.Data.Module "blog-category")}} active{{end}}" href="/blog/">Blog</a>
+								<a class="nav-link{{if eq $.Data.Module "404"}} active{{end}}" href="/not-existent-page/">404</a>
 							</li>
 						</ul>
 					</div>

+ 1 - 1
hosts/localhost/template/sidebar-right.html

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

+ 18 - 9
logger/handler.go

@@ -30,15 +30,24 @@ func (this handler) log(w *writer, r *http.Request) {
 	}, " "))
 
 	// Do not wait
-	go func() {
-		select {
-		case this.c <- logMsg{r.Host, msg, w.status >= 400}:
-			return
-		case <-time.After(1 * time.Second):
-			fmt.Println("Logger error, log channel is overflowed (2)")
-			return
-		}
-	}()
+	// go func() {
+	// 	select {
+	// 	case this.c <- logMsg{r.Host, msg, w.status >= 400}:
+	// 		return
+	// 	case <-time.After(1 * time.Second):
+	// 		fmt.Println("Logger error, log channel is overflowed (2)")
+	// 		return
+	// 	}
+	// }()
+
+	// Wait
+	select {
+	case this.c <- logMsg{r.Host, msg, w.status >= 400}:
+		return
+	case <-time.After(1 * time.Second):
+		fmt.Println("Logger error, log channel is overflowed (2)")
+		return
+	}
 }
 
 func (this handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

+ 18 - 9
logger/logger.go

@@ -136,15 +136,24 @@ func (this *Logger) Log(msg string, r *http.Request, isError bool, vars ...inter
 	}
 
 	// Do not wait
-	go func() {
-		select {
-		case this.cdata <- logMsg{host, msg, isError}:
-			return
-		case <-time.After(1 * time.Second):
-			fmt.Println("Logger error, log channel is overflowed (1)")
-			return
-		}
-	}()
+	// go func() {
+	// 	select {
+	// 	case this.cdata <- logMsg{host, msg, isError}:
+	// 		return
+	// 	case <-time.After(1 * time.Second):
+	// 		fmt.Println("Logger error, log channel is overflowed (1)")
+	// 		return
+	// 	}
+	// }()
+
+	// Wait
+	select {
+	case this.cdata <- logMsg{host, msg, isError}:
+		return
+	case <-time.After(1 * time.Second):
+		fmt.Println("Logger error, log channel is overflowed (1)")
+		return
+	}
 }
 
 func (this *Logger) SetWwwDir(dir string) {

+ 11 - 2
modules/module_blog.go

@@ -80,7 +80,7 @@ func (this *Modules) RegisterModule_Blog() *Module {
 			return
 		} else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "blog" && wrap.UrlArgs[1] != "" {
 			// Blog post
-			row := &utils.MySql_blog_posts{}
+			row := &utils.MySql_blog_post{}
 			err := wrap.DB.QueryRow(`
 				SELECT
 					id,
@@ -312,7 +312,7 @@ func (this *Modules) RegisterModule_Blog() *Module {
 				})
 			}
 
-			data := utils.MySql_blog_posts{
+			data := utils.MySql_blog_post{
 				A_id:       0,
 				A_user:     0,
 				A_name:     "",
@@ -335,6 +335,7 @@ func (this *Modules) RegisterModule_Blog() *Module {
 						user,
 						name,
 						alias,
+						briefly,
 						content,
 						active
 					FROM
@@ -348,6 +349,7 @@ func (this *Modules) RegisterModule_Blog() *Module {
 					&data.A_user,
 					&data.A_name,
 					&data.A_alias,
+					&data.A_briefly,
 					&data.A_content,
 					&data.A_active,
 				)
@@ -428,6 +430,13 @@ func (this *Modules) RegisterModule_Blog() *Module {
 						</div>`
 					},
 				},
+				{
+					Kind:    builder.DFKTextArea,
+					Caption: "Briefly",
+					Name:    "briefly",
+					Value:   data.A_briefly,
+					Classes: "briefly autosize",
+				},
 				{
 					Kind:    builder.DFKTextArea,
 					Caption: "Post content",

+ 5 - 0
modules/module_blog_act_modify.go

@@ -17,6 +17,7 @@ func (this *Modules) RegisterAction_BlogModify() *Action {
 		pf_id := wrap.R.FormValue("id")
 		pf_name := wrap.R.FormValue("name")
 		pf_alias := wrap.R.FormValue("alias")
+		pf_briefly := wrap.R.FormValue("briefly")
 		pf_content := wrap.R.FormValue("content")
 		pf_active := wrap.R.FormValue("active")
 
@@ -51,6 +52,7 @@ func (this *Modules) RegisterAction_BlogModify() *Action {
 						user = ?,
 						name = ?,
 						alias = ?,
+						briefly = ?,
 						content = ?,
 						datetime = ?,
 						active = ?
@@ -58,6 +60,7 @@ func (this *Modules) RegisterAction_BlogModify() *Action {
 					wrap.User.A_id,
 					pf_name,
 					pf_alias,
+					pf_briefly,
 					pf_content,
 					utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
 					pf_active,
@@ -130,6 +133,7 @@ func (this *Modules) RegisterAction_BlogModify() *Action {
 					`UPDATE blog_posts SET
 						name = ?,
 						alias = ?,
+						briefly = ?,
 						content = ?,
 						active = ?
 					WHERE
@@ -137,6 +141,7 @@ func (this *Modules) RegisterAction_BlogModify() *Action {
 					;`,
 					pf_name,
 					pf_alias,
+					pf_briefly,
 					pf_content,
 					pf_active,
 					utils.StrToInt(pf_id),

+ 0 - 130
modules/module_index.go

@@ -294,133 +294,3 @@ func (this *Modules) RegisterModule_Index() *Module {
 		return this.getSidebarModules(wrap), content, sidebar
 	})
 }
-
-func (this *Modules) RegisterAction_IndexModify() *Action {
-	return this.newAction(AInfo{
-		WantDB:    true,
-		Mount:     "index-modify",
-		WantAdmin: true,
-	}, func(wrap *wrapper.Wrapper) {
-		pf_id := wrap.R.FormValue("id")
-		pf_name := wrap.R.FormValue("name")
-		pf_alias := wrap.R.FormValue("alias")
-		pf_content := wrap.R.FormValue("content")
-		pf_meta_title := wrap.R.FormValue("meta_title")
-		pf_meta_keywords := wrap.R.FormValue("meta_keywords")
-		pf_meta_description := wrap.R.FormValue("meta_description")
-		pf_active := wrap.R.FormValue("active")
-
-		if pf_active == "" {
-			pf_active = "0"
-		}
-
-		if !utils.IsNumeric(pf_id) {
-			wrap.MsgError(`Inner system error`)
-			return
-		}
-
-		if pf_name == "" {
-			wrap.MsgError(`Please specify page name`)
-			return
-		}
-
-		if pf_alias == "" {
-			pf_alias = utils.GenerateAlias(pf_name)
-		}
-
-		if !utils.IsValidAlias(pf_alias) {
-			wrap.MsgError(`Please specify correct page alias`)
-			return
-		}
-
-		if pf_id == "0" {
-			// Add new page
-			_, err := wrap.DB.Exec(
-				`INSERT INTO pages SET
-					user = ?,
-					name = ?,
-					alias = ?,
-					content = ?,
-					meta_title = ?,
-					meta_keywords = ?,
-					meta_description = ?,
-					datetime = ?,
-					active = ?
-				;`,
-				wrap.User.A_id,
-				pf_name,
-				pf_alias,
-				pf_content,
-				pf_meta_title,
-				pf_meta_keywords,
-				pf_meta_description,
-				utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
-				pf_active,
-			)
-			if err != nil {
-				wrap.MsgError(err.Error())
-				return
-			}
-			wrap.Write(`window.location='/cp/';`)
-		} else {
-			// Update page
-			_, err := wrap.DB.Exec(
-				`UPDATE pages SET
-					name = ?,
-					alias = ?,
-					content = ?,
-					meta_title = ?,
-					meta_keywords = ?,
-					meta_description = ?,
-					active = ?
-				WHERE
-					id = ?
-				;`,
-				pf_name,
-				pf_alias,
-				pf_content,
-				pf_meta_title,
-				pf_meta_keywords,
-				pf_meta_description,
-				pf_active,
-				utils.StrToInt(pf_id),
-			)
-			if err != nil {
-				wrap.MsgError(err.Error())
-				return
-			}
-			wrap.Write(`window.location='/cp/index/modify/` + pf_id + `/';`)
-		}
-	})
-}
-
-func (this *Modules) RegisterAction_IndexDelete() *Action {
-	return this.newAction(AInfo{
-		WantDB:    true,
-		Mount:     "index-delete",
-		WantAdmin: true,
-	}, func(wrap *wrapper.Wrapper) {
-		pf_id := wrap.R.FormValue("id")
-
-		if !utils.IsNumeric(pf_id) {
-			wrap.MsgError(`Inner system error`)
-			return
-		}
-
-		err := wrap.DB.Transaction(func(tx *wrapper.Tx) error {
-			// Process
-			if _, err := tx.Exec("DELETE FROM pages WHERE id = ?;", pf_id); err != nil {
-				return err
-			}
-			return nil
-		})
-
-		if err != nil {
-			wrap.MsgError(err.Error())
-			return
-		}
-
-		// Reload current page
-		wrap.Write(`window.location.reload(false);`)
-	})
-}

+ 37 - 0
modules/module_index_act_delete.go

@@ -0,0 +1,37 @@
+package modules
+
+import (
+	"golang-fave/engine/wrapper"
+	"golang-fave/utils"
+)
+
+func (this *Modules) RegisterAction_IndexDelete() *Action {
+	return this.newAction(AInfo{
+		WantDB:    true,
+		Mount:     "index-delete",
+		WantAdmin: true,
+	}, func(wrap *wrapper.Wrapper) {
+		pf_id := wrap.R.FormValue("id")
+
+		if !utils.IsNumeric(pf_id) {
+			wrap.MsgError(`Inner system error`)
+			return
+		}
+
+		err := wrap.DB.Transaction(func(tx *wrapper.Tx) error {
+			// Process
+			if _, err := tx.Exec("DELETE FROM pages WHERE id = ?;", pf_id); err != nil {
+				return err
+			}
+			return nil
+		})
+
+		if err != nil {
+			wrap.MsgError(err.Error())
+			return
+		}
+
+		// Reload current page
+		wrap.Write(`window.location.reload(false);`)
+	})
+}

+ 105 - 0
modules/module_index_act_modify.go

@@ -0,0 +1,105 @@
+package modules
+
+import (
+	"golang-fave/engine/wrapper"
+	"golang-fave/utils"
+)
+
+func (this *Modules) RegisterAction_IndexModify() *Action {
+	return this.newAction(AInfo{
+		WantDB:    true,
+		Mount:     "index-modify",
+		WantAdmin: true,
+	}, func(wrap *wrapper.Wrapper) {
+		pf_id := wrap.R.FormValue("id")
+		pf_name := wrap.R.FormValue("name")
+		pf_alias := wrap.R.FormValue("alias")
+		pf_content := wrap.R.FormValue("content")
+		pf_meta_title := wrap.R.FormValue("meta_title")
+		pf_meta_keywords := wrap.R.FormValue("meta_keywords")
+		pf_meta_description := wrap.R.FormValue("meta_description")
+		pf_active := wrap.R.FormValue("active")
+
+		if pf_active == "" {
+			pf_active = "0"
+		}
+
+		if !utils.IsNumeric(pf_id) {
+			wrap.MsgError(`Inner system error`)
+			return
+		}
+
+		if pf_name == "" {
+			wrap.MsgError(`Please specify page name`)
+			return
+		}
+
+		if pf_alias == "" {
+			pf_alias = utils.GenerateAlias(pf_name)
+		}
+
+		if !utils.IsValidAlias(pf_alias) {
+			wrap.MsgError(`Please specify correct page alias`)
+			return
+		}
+
+		if pf_id == "0" {
+			// Add new page
+			_, err := wrap.DB.Exec(
+				`INSERT INTO pages SET
+					user = ?,
+					name = ?,
+					alias = ?,
+					content = ?,
+					meta_title = ?,
+					meta_keywords = ?,
+					meta_description = ?,
+					datetime = ?,
+					active = ?
+				;`,
+				wrap.User.A_id,
+				pf_name,
+				pf_alias,
+				pf_content,
+				pf_meta_title,
+				pf_meta_keywords,
+				pf_meta_description,
+				utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
+				pf_active,
+			)
+			if err != nil {
+				wrap.MsgError(err.Error())
+				return
+			}
+			wrap.Write(`window.location='/cp/';`)
+		} else {
+			// Update page
+			_, err := wrap.DB.Exec(
+				`UPDATE pages SET
+					name = ?,
+					alias = ?,
+					content = ?,
+					meta_title = ?,
+					meta_keywords = ?,
+					meta_description = ?,
+					active = ?
+				WHERE
+					id = ?
+				;`,
+				pf_name,
+				pf_alias,
+				pf_content,
+				pf_meta_title,
+				pf_meta_keywords,
+				pf_meta_description,
+				pf_active,
+				utils.StrToInt(pf_id),
+			)
+			if err != nil {
+				wrap.MsgError(err.Error())
+				return
+			}
+			wrap.Write(`window.location='/cp/index/modify/` + pf_id + `/';`)
+		}
+	})
+}

+ 7 - 0
modules/module_index_act_mysql_setup.go

@@ -156,6 +156,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 				user int(11) NOT NULL COMMENT 'User id',
 				name varchar(255) NOT NULL COMMENT 'Post name',
 				alias varchar(255) NOT NULL COMMENT 'Post alias',
+				briefly text NOT NULL COMMENT 'Post brief content',
 				content text NOT NULL COMMENT 'Post content',
 				datetime datetime NOT NULL COMMENT 'Creation date/time',
 				active int(1) NOT NULL COMMENT 'Is active post or not',
@@ -172,6 +173,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 				user = ?,
 				name = ?,
 				alias = ?,
+				briefly = ?,
 				content = ?,
 				datetime = ?,
 				active = ?
@@ -180,6 +182,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			1,
 			"Why should we eat wholesome food?",
 			"why-should-we-eat-wholesome-food",
+			"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua...</p>",
 			"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Feugiat in ante metus dictum at tempor commodo ullamcorper a. Et malesuada fames ac turpis egestas sed tempus urna et. Euismod elementum nisi quis eleifend. Nisi porta lorem mollis aliquam ut porttitor. Ac turpis egestas maecenas pharetra convallis posuere. Nunc non blandit massa enim nec dui. Commodo elit at imperdiet dui accumsan sit amet nulla. Viverra accumsan in nisl nisi scelerisque. Dui nunc mattis enim ut tellus. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Faucibus ornare suspendisse sed nisi lacus. Nulla facilisi morbi tempus iaculis. Ut eu sem integer vitae justo eget magna fermentum iaculis. Ullamcorper sit amet risus nullam eget felis eget nunc. Volutpat sed cras ornare arcu dui vivamus. Eget magna fermentum iaculis eu non diam.</p><p>Arcu ac tortor dignissim convallis aenean et tortor. Vitae auctor eu augue ut lectus arcu. Ac turpis egestas integer eget aliquet nibh praesent. Interdum velit euismod in pellentesque massa placerat duis. Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Nisl rhoncus mattis rhoncus urna neque viverra justo. Odio ut enim blandit volutpat. Ac auctor augue mauris augue neque gravida. Ut lectus arcu bibendum at varius vel. Porttitor leo a diam sollicitudin tempor id eu nisl nunc. Dolor sit amet consectetur adipiscing elit duis tristique. Semper quis lectus nulla at volutpat diam ut. Sapien eget mi proin sed.</p>",
 			utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
 			1,
@@ -194,6 +197,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 				user = ?,
 				name = ?,
 				alias = ?,
+				briefly = ?,
 				content = ?,
 				datetime = ?,
 				active = ?
@@ -202,6 +206,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			1,
 			"Latest top space movies",
 			"latest-top-space-movies",
+			"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua...</p>",
 			"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Feugiat in ante metus dictum at tempor commodo ullamcorper a. Et malesuada fames ac turpis egestas sed tempus urna et. Euismod elementum nisi quis eleifend. Nisi porta lorem mollis aliquam ut porttitor. Ac turpis egestas maecenas pharetra convallis posuere. Nunc non blandit massa enim nec dui. Commodo elit at imperdiet dui accumsan sit amet nulla. Viverra accumsan in nisl nisi scelerisque. Dui nunc mattis enim ut tellus. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Faucibus ornare suspendisse sed nisi lacus. Nulla facilisi morbi tempus iaculis. Ut eu sem integer vitae justo eget magna fermentum iaculis. Ullamcorper sit amet risus nullam eget felis eget nunc. Volutpat sed cras ornare arcu dui vivamus. Eget magna fermentum iaculis eu non diam.</p><p>Arcu ac tortor dignissim convallis aenean et tortor. Vitae auctor eu augue ut lectus arcu. Ac turpis egestas integer eget aliquet nibh praesent. Interdum velit euismod in pellentesque massa placerat duis. Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Nisl rhoncus mattis rhoncus urna neque viverra justo. Odio ut enim blandit volutpat. Ac auctor augue mauris augue neque gravida. Ut lectus arcu bibendum at varius vel. Porttitor leo a diam sollicitudin tempor id eu nisl nunc. Dolor sit amet consectetur adipiscing elit duis tristique. Semper quis lectus nulla at volutpat diam ut. Sapien eget mi proin sed.</p>",
 			utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
 			1,
@@ -216,6 +221,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 				user = ?,
 				name = ?,
 				alias = ?,
+				briefly = ?,
 				content = ?,
 				datetime = ?,
 				active = ?
@@ -224,6 +230,7 @@ func (this *Modules) RegisterAction_IndexMysqlSetup() *Action {
 			1,
 			"The best juices for a child",
 			"the-best-juices-for-a-child",
+			"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua...</p>",
 			"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Feugiat in ante metus dictum at tempor commodo ullamcorper a. Et malesuada fames ac turpis egestas sed tempus urna et. Euismod elementum nisi quis eleifend. Nisi porta lorem mollis aliquam ut porttitor. Ac turpis egestas maecenas pharetra convallis posuere. Nunc non blandit massa enim nec dui. Commodo elit at imperdiet dui accumsan sit amet nulla. Viverra accumsan in nisl nisi scelerisque. Dui nunc mattis enim ut tellus. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Faucibus ornare suspendisse sed nisi lacus. Nulla facilisi morbi tempus iaculis. Ut eu sem integer vitae justo eget magna fermentum iaculis. Ullamcorper sit amet risus nullam eget felis eget nunc. Volutpat sed cras ornare arcu dui vivamus. Eget magna fermentum iaculis eu non diam.</p><p>Arcu ac tortor dignissim convallis aenean et tortor. Vitae auctor eu augue ut lectus arcu. Ac turpis egestas integer eget aliquet nibh praesent. Interdum velit euismod in pellentesque massa placerat duis. Vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Nisl rhoncus mattis rhoncus urna neque viverra justo. Odio ut enim blandit volutpat. Ac auctor augue mauris augue neque gravida. Ut lectus arcu bibendum at varius vel. Porttitor leo a diam sollicitudin tempor id eu nisl nunc. Dolor sit amet consectetur adipiscing elit duis tristique. Semper quis lectus nulla at volutpat diam ut. Sapien eget mi proin sed.</p>",
 			utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
 			1,

+ 1 - 0
support/schema.sql

@@ -24,6 +24,7 @@ CREATE TABLE `blog_posts` (
 	`user` int(11) NOT NULL COMMENT 'User id',
 	`name` varchar(255) NOT NULL COMMENT 'Post name',
 	`alias` varchar(255) NOT NULL COMMENT 'Post alias',
+	`briefly` text NOT NULL COMMENT 'Post brief content',
 	`content` text NOT NULL COMMENT 'Post content',
 	`datetime` datetime NOT NULL COMMENT 'Creation date/time',
 	`active` int(1) NOT NULL COMMENT 'Is active post or not',

BIN
support/screenshots-1.gif


+ 0 - 0
utils/mysql_struct_blog_categories.go → utils/mysql_struct_blog_category.go


+ 2 - 1
utils/mysql_struct_blog_posts.go → utils/mysql_struct_blog_post.go

@@ -1,10 +1,11 @@
 package utils
 
-type MySql_blog_posts struct {
+type MySql_blog_post struct {
 	A_id       int
 	A_user     int
 	A_name     string
 	A_alias    string
+	A_briefly  string
 	A_content  string
 	A_datetime int
 	A_active   int

+ 0 - 0
utils/mysql_struct_pages.go → utils/mysql_struct_page.go


+ 0 - 0
utils/mysql_struct_users.go → utils/mysql_struct_user.go


+ 12 - 0
utils/utils.go

@@ -57,6 +57,18 @@ func IsValidEmail(email string) bool {
 }
 
 func IsValidAlias(alias string) bool {
+	// Control panel
+	regexpeCP := regexp.MustCompile(`^\/cp\/`)
+	if alias == "/cp" || regexpeCP.MatchString(alias) {
+		return false
+	}
+
+	// Blog module
+	regexpeBlog := regexp.MustCompile(`^\/blog\/`)
+	if alias == "/blog" || regexpeBlog.MatchString(alias) {
+		return false
+	}
+
 	regexpeSlash := regexp.MustCompile(`[\/]{2,}`)
 	regexpeChars := regexp.MustCompile(`^\/([a-zA-Z0-9\/\-_\.]+)\/?$`)
 	return (!regexpeSlash.MatchString(alias) && regexpeChars.MatchString(alias)) || alias == "/"

+ 12 - 0
utils/utils_test.go

@@ -56,6 +56,18 @@ func TestIsValidAlias(t *testing.T) {
 	Expect(t, IsValidAlias(""), false)
 	Expect(t, IsValidAlias("some-page"), false)
 	Expect(t, IsValidAlias("/some page/"), false)
+
+	Expect(t, IsValidAlias("/cp"), false)
+	Expect(t, IsValidAlias("/cp/"), false)
+	Expect(t, IsValidAlias("/cp/some"), false)
+	Expect(t, IsValidAlias("/cp-1"), true)
+	Expect(t, IsValidAlias("/cp-some"), true)
+
+	Expect(t, IsValidAlias("/blog"), false)
+	Expect(t, IsValidAlias("/blog/"), false)
+	Expect(t, IsValidAlias("/blog/some"), false)
+	Expect(t, IsValidAlias("/blog-1"), true)
+	Expect(t, IsValidAlias("/blog-some"), true)
 }
 
 func TestIsValidSingleAlias(t *testing.T) {

Some files were not shown because too many files changed in this diff