module_blog.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. package modules
  2. import (
  3. "database/sql"
  4. _ "github.com/go-sql-driver/mysql"
  5. "html"
  6. "strings"
  7. "golang-fave/assets"
  8. "golang-fave/consts"
  9. "golang-fave/engine/builder"
  10. "golang-fave/engine/wrapper"
  11. "golang-fave/utils"
  12. )
  13. func (this *Modules) RegisterModule_Blog() *Module {
  14. return this.newModule(MInfo{
  15. WantDB: true,
  16. Mount: "blog",
  17. Name: "Blog",
  18. Order: 1,
  19. System: false,
  20. Icon: assets.SysSvgIconList,
  21. Sub: &[]MISub{
  22. {Mount: "default", Name: "List of posts", Show: true, Icon: assets.SysSvgIconList},
  23. {Mount: "add", Name: "Add new post", Show: true, Icon: assets.SysSvgIconPlus},
  24. {Mount: "modify", Name: "Modify post", Show: false},
  25. {Sep: true, Show: true},
  26. {Mount: "categories", Name: "List of categories", Show: true, Icon: assets.SysSvgIconList},
  27. {Mount: "categories-add", Name: "Add new category", Show: true, Icon: assets.SysSvgIconPlus},
  28. {Mount: "categories-modify", Name: "Modify category", Show: false},
  29. },
  30. }, nil, func(wrap *wrapper.Wrapper) (string, string, string) {
  31. content := ""
  32. sidebar := ""
  33. if wrap.CurrSubModule == "" || wrap.CurrSubModule == "default" {
  34. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  35. {Name: "List of posts"},
  36. })
  37. content += builder.DataTable(
  38. wrap,
  39. "blog_posts",
  40. "id",
  41. "DESC",
  42. &[]builder.DataTableRow{
  43. {
  44. DBField: "id",
  45. },
  46. {
  47. DBField: "name",
  48. NameInTable: "Post / URL",
  49. CallBack: func(values *[]string) string {
  50. name := `<a href="/cp/` + wrap.CurrModule + `/modify/` + (*values)[0] + `/">` + html.EscapeString((*values)[1]) + `</a>`
  51. alias := html.EscapeString((*values)[2])
  52. return `<div>` + name + `</div><div><small>/blog/` + alias + `/</small></div>`
  53. },
  54. },
  55. {
  56. DBField: "alias",
  57. },
  58. {
  59. DBField: "datetime",
  60. DBExp: "UNIX_TIMESTAMP(`datetime`)",
  61. NameInTable: "Date / Time",
  62. Classes: "d-none d-md-table-cell",
  63. CallBack: func(values *[]string) string {
  64. t := int64(utils.StrToInt((*values)[3]))
  65. return `<div>` + utils.UnixTimestampToFormat(t, "02.01.2006") + `</div>` +
  66. `<div><small>` + utils.UnixTimestampToFormat(t, "15:04:05") + `</small></div>`
  67. },
  68. },
  69. {
  70. DBField: "active",
  71. NameInTable: "Active",
  72. Classes: "d-none d-sm-table-cell",
  73. CallBack: func(values *[]string) string {
  74. return builder.CheckBox(utils.StrToInt((*values)[4]))
  75. },
  76. },
  77. },
  78. func(values *[]string) string {
  79. return builder.DataTableAction(&[]builder.DataTableActionRow{
  80. {
  81. Icon: assets.SysSvgIconView,
  82. Href: `/blog/` + (*values)[2] + `/`,
  83. Hint: "View",
  84. Target: "_blank",
  85. },
  86. {
  87. Icon: assets.SysSvgIconEdit,
  88. Href: "/cp/" + wrap.CurrModule + "/modify/" + (*values)[0] + "/",
  89. Hint: "Edit",
  90. },
  91. {
  92. Icon: assets.SysSvgIconRemove,
  93. Href: "javascript:fave.ActionDataTableDelete(this,'blog-delete','" +
  94. (*values)[0] + "','Are you sure want to delete post?');",
  95. Hint: "Delete",
  96. Classes: "delete",
  97. },
  98. })
  99. },
  100. "/cp/"+wrap.CurrModule+"/",
  101. nil,
  102. nil,
  103. true,
  104. )
  105. } else if wrap.CurrSubModule == "categories" {
  106. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  107. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/" + wrap.CurrSubModule + "/"},
  108. {Name: "List of categories"},
  109. })
  110. content += builder.DataTable(
  111. wrap,
  112. "blog_cats",
  113. "id",
  114. "ASC",
  115. &[]builder.DataTableRow{
  116. {
  117. DBField: "id",
  118. },
  119. {
  120. DBField: "user",
  121. },
  122. {
  123. DBField: "name",
  124. NameInTable: "Category",
  125. CallBack: func(values *[]string) string {
  126. depth := utils.StrToInt((*values)[4]) - 1
  127. if depth < 0 {
  128. depth = 0
  129. }
  130. sub := strings.Repeat("&mdash; ", depth)
  131. name := `<a href="/cp/` + wrap.CurrModule + `/categories-modify/` + (*values)[0] + `/">` + sub + html.EscapeString((*values)[2]) + `</a>`
  132. return `<div>` + name + `</div>`
  133. },
  134. },
  135. {
  136. DBField: "alias",
  137. },
  138. {
  139. DBField: "depth",
  140. },
  141. },
  142. func(values *[]string) string {
  143. return builder.DataTableAction(&[]builder.DataTableActionRow{
  144. {
  145. Icon: assets.SysSvgIconEdit,
  146. Href: "/cp/" + wrap.CurrModule + "/categories-modify/" + (*values)[0] + "/",
  147. Hint: "Edit",
  148. },
  149. {
  150. Icon: assets.SysSvgIconRemove,
  151. Href: "javascript:fave.ActionDataTableDelete(this,'blog-categories-delete','" +
  152. (*values)[0] + "','Are you sure want to delete category?');",
  153. Hint: "Delete",
  154. Classes: "delete",
  155. },
  156. })
  157. },
  158. "/cp/"+wrap.CurrModule+"/"+wrap.CurrSubModule+"/",
  159. nil,
  160. func(limit_offset int, pear_page int) (*sql.Rows, error) {
  161. return wrap.DB.Query(
  162. `SELECT
  163. node.id,
  164. node.user,
  165. node.name,
  166. node.alias,
  167. (COUNT(parent.id) - 1) AS depth
  168. FROM
  169. blog_cats AS node,
  170. blog_cats AS parent
  171. WHERE
  172. node.lft BETWEEN parent.lft AND parent.rgt AND
  173. node.id > 1
  174. GROUP BY
  175. node.id
  176. ORDER BY
  177. node.lft ASC
  178. ;`,
  179. )
  180. },
  181. false,
  182. )
  183. } else if wrap.CurrSubModule == "add" || wrap.CurrSubModule == "modify" {
  184. if wrap.CurrSubModule == "add" {
  185. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  186. {Name: "Add new post"},
  187. })
  188. } else {
  189. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  190. {Name: "Modify post"},
  191. })
  192. }
  193. data := utils.MySql_blog_posts{
  194. A_id: 0,
  195. A_user: 0,
  196. A_name: "",
  197. A_alias: "",
  198. A_content: "",
  199. A_datetime: 0,
  200. A_active: 0,
  201. }
  202. if wrap.CurrSubModule == "modify" {
  203. if len(wrap.UrlArgs) != 3 {
  204. return "", "", ""
  205. }
  206. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  207. return "", "", ""
  208. }
  209. err := wrap.DB.QueryRow(`
  210. SELECT
  211. id,
  212. user,
  213. name,
  214. alias,
  215. content,
  216. active
  217. FROM
  218. blog_posts
  219. WHERE
  220. id = ?
  221. LIMIT 1;`,
  222. utils.StrToInt(wrap.UrlArgs[2]),
  223. ).Scan(
  224. &data.A_id,
  225. &data.A_user,
  226. &data.A_name,
  227. &data.A_alias,
  228. &data.A_content,
  229. &data.A_active,
  230. )
  231. if err != nil {
  232. return "", "", ""
  233. }
  234. }
  235. btn_caption := "Add"
  236. if wrap.CurrSubModule == "modify" {
  237. btn_caption = "Save"
  238. }
  239. content += builder.DataForm(wrap, []builder.DataFormField{
  240. {
  241. Kind: builder.DFKHidden,
  242. Name: "action",
  243. Value: "blog-modify",
  244. },
  245. {
  246. Kind: builder.DFKHidden,
  247. Name: "id",
  248. Value: utils.IntToStr(data.A_id),
  249. },
  250. {
  251. Kind: builder.DFKText,
  252. Caption: "Post name",
  253. Name: "name",
  254. Value: data.A_name,
  255. },
  256. {
  257. Kind: builder.DFKText,
  258. Caption: "Post alias",
  259. Name: "alias",
  260. Value: data.A_alias,
  261. Hint: "Example: our-news",
  262. },
  263. {
  264. Kind: builder.DFKText,
  265. Caption: "Categories",
  266. Name: "cats",
  267. Value: "0",
  268. CallBack: func(field *builder.DataFormField) string {
  269. return `<div class="form-group n4">
  270. <div class="row">
  271. <div class="col-md-3">
  272. <label for="lbl_parent">Categories</label>
  273. </div>
  274. <div class="col-md-9">
  275. <div>
  276. <select class="form-control" id="lbl_cats" name="cats[]" multiple>
  277. <!--<option value=""></option>-->
  278. ` + this.blog_GetCategorySelectOptions(wrap, 0, 0, []int{}) + `
  279. </select>
  280. </div>
  281. </div>
  282. </div>
  283. </div>`
  284. },
  285. },
  286. {
  287. Kind: builder.DFKTextArea,
  288. Caption: "Post content",
  289. Name: "content",
  290. Value: data.A_content,
  291. Classes: "autosize",
  292. },
  293. {
  294. Kind: builder.DFKCheckBox,
  295. Caption: "Active",
  296. Name: "active",
  297. Value: utils.IntToStr(data.A_active),
  298. },
  299. {
  300. Kind: builder.DFKMessage,
  301. },
  302. {
  303. Kind: builder.DFKSubmit,
  304. Value: btn_caption,
  305. Target: "add-edit-button",
  306. },
  307. })
  308. if wrap.CurrSubModule == "add" {
  309. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  310. } else {
  311. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  312. }
  313. } else if wrap.CurrSubModule == "categories-add" || wrap.CurrSubModule == "categories-modify" {
  314. if wrap.CurrSubModule == "categories-add" {
  315. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  316. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  317. {Name: "Add new category"},
  318. })
  319. } else {
  320. content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
  321. {Name: "Categories", Link: "/cp/" + wrap.CurrModule + "/categories/"},
  322. {Name: "Modify category"},
  323. })
  324. }
  325. data := utils.MySql_blog_category{
  326. A_id: 0,
  327. A_user: 0,
  328. A_name: "",
  329. A_alias: "",
  330. A_lft: 0,
  331. A_rgt: 0,
  332. }
  333. if wrap.CurrSubModule == "categories-modify" {
  334. if len(wrap.UrlArgs) != 3 {
  335. return "", "", ""
  336. }
  337. if !utils.IsNumeric(wrap.UrlArgs[2]) {
  338. return "", "", ""
  339. }
  340. err := wrap.DB.QueryRow(`
  341. SELECT
  342. id,
  343. user,
  344. name,
  345. alias,
  346. lft,
  347. rgt
  348. FROM
  349. blog_cats
  350. WHERE
  351. id = ?
  352. LIMIT 1;`,
  353. utils.StrToInt(wrap.UrlArgs[2]),
  354. ).Scan(
  355. &data.A_id,
  356. &data.A_user,
  357. &data.A_name,
  358. &data.A_alias,
  359. &data.A_lft,
  360. &data.A_rgt,
  361. )
  362. if err != nil {
  363. return "", "", ""
  364. }
  365. }
  366. btn_caption := "Add"
  367. if wrap.CurrSubModule == "categories-modify" {
  368. btn_caption = "Save"
  369. }
  370. parentId := 0
  371. if wrap.CurrSubModule == "categories-modify" {
  372. parentId = this.blog_GetCategoryParentId(wrap, data.A_id)
  373. }
  374. content += builder.DataForm(wrap, []builder.DataFormField{
  375. {
  376. Kind: builder.DFKHidden,
  377. Name: "action",
  378. Value: "blog-categories-modify",
  379. },
  380. {
  381. Kind: builder.DFKHidden,
  382. Name: "id",
  383. Value: utils.IntToStr(data.A_id),
  384. },
  385. {
  386. Kind: builder.DFKText,
  387. Caption: "Parent",
  388. Name: "parent",
  389. Value: "0",
  390. CallBack: func(field *builder.DataFormField) string {
  391. return `<div class="form-group n2">
  392. <div class="row">
  393. <div class="col-md-3">
  394. <label for="lbl_parent">Parent</label>
  395. </div>
  396. <div class="col-md-9">
  397. <div>
  398. <select class="form-control" id="lbl_parent" name="parent">
  399. <option value="0">&mdash;</option>
  400. ` + this.blog_GetCategorySelectOptions(wrap, data.A_id, parentId, []int{}) + `
  401. </select>
  402. </div>
  403. </div>
  404. </div>
  405. </div>`
  406. },
  407. },
  408. {
  409. Kind: builder.DFKText,
  410. Caption: "Name",
  411. Name: "name",
  412. Value: data.A_name,
  413. },
  414. {
  415. Kind: builder.DFKText,
  416. Caption: "Alias",
  417. Name: "alias",
  418. Value: data.A_alias,
  419. Hint: "Example: popular-posts",
  420. },
  421. {
  422. Kind: builder.DFKMessage,
  423. },
  424. {
  425. Kind: builder.DFKSubmit,
  426. Value: btn_caption,
  427. Target: "add-edit-button",
  428. },
  429. })
  430. if wrap.CurrSubModule == "categories-add" {
  431. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Add</button>`
  432. } else {
  433. sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
  434. }
  435. }
  436. return this.getSidebarModules(wrap), content, sidebar
  437. })
  438. }
  439. func (this *Modules) RegisterAction_BlogModify() *Action {
  440. return this.newAction(AInfo{
  441. WantDB: true,
  442. Mount: "blog-modify",
  443. WantAdmin: true,
  444. }, func(wrap *wrapper.Wrapper) {
  445. pf_id := wrap.R.FormValue("id")
  446. pf_name := wrap.R.FormValue("name")
  447. pf_alias := wrap.R.FormValue("alias")
  448. pf_content := wrap.R.FormValue("content")
  449. pf_active := wrap.R.FormValue("active")
  450. if pf_active == "" {
  451. pf_active = "0"
  452. }
  453. if !utils.IsNumeric(pf_id) {
  454. wrap.MsgError(`Inner system error`)
  455. return
  456. }
  457. if pf_name == "" {
  458. wrap.MsgError(`Please specify page name`)
  459. return
  460. }
  461. if pf_alias == "" {
  462. pf_alias = utils.GenerateSingleAlias(pf_name)
  463. }
  464. if !utils.IsValidSingleAlias(pf_alias) {
  465. wrap.MsgError(`Please specify correct post alias`)
  466. return
  467. }
  468. if pf_id == "0" {
  469. // Add new post
  470. _, err := wrap.DB.Exec(
  471. `INSERT INTO blog_posts SET
  472. user = ?,
  473. name = ?,
  474. alias = ?,
  475. content = ?,
  476. datetime = ?,
  477. active = ?
  478. ;`,
  479. wrap.User.A_id,
  480. pf_name,
  481. pf_alias,
  482. pf_content,
  483. utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
  484. pf_active,
  485. )
  486. if err != nil {
  487. wrap.MsgError(err.Error())
  488. return
  489. }
  490. wrap.Write(`window.location='/cp/blog/';`)
  491. } else {
  492. // Update post
  493. _, err := wrap.DB.Exec(
  494. `UPDATE blog_posts SET
  495. name = ?,
  496. alias = ?,
  497. content = ?,
  498. active = ?
  499. WHERE
  500. id = ?
  501. ;`,
  502. pf_name,
  503. pf_alias,
  504. pf_content,
  505. pf_active,
  506. utils.StrToInt(pf_id),
  507. )
  508. if err != nil {
  509. wrap.MsgError(err.Error())
  510. return
  511. }
  512. wrap.Write(`window.location='/cp/blog/modify/` + pf_id + `/';`)
  513. }
  514. })
  515. }
  516. func (this *Modules) RegisterAction_BlogDelete() *Action {
  517. return this.newAction(AInfo{
  518. WantDB: true,
  519. Mount: "blog-delete",
  520. WantAdmin: true,
  521. }, func(wrap *wrapper.Wrapper) {
  522. pf_id := wrap.R.FormValue("id")
  523. if !utils.IsNumeric(pf_id) {
  524. wrap.MsgError(`Inner system error`)
  525. return
  526. }
  527. // Start transaction with table lock
  528. _, err := wrap.DB.Exec("LOCK TABLES blog_posts WRITE, blog_cat_post_rel WRITE;")
  529. if err != nil {
  530. wrap.MsgError(err.Error())
  531. return
  532. }
  533. tx, err := wrap.DB.Begin()
  534. if err != nil {
  535. wrap.MsgError(err.Error())
  536. return
  537. }
  538. // Delete target post with category connection data
  539. if _, err = tx.Exec("DELETE FROM blog_cat_post_rel WHERE post_id = ?;", pf_id); err != nil {
  540. tx.Rollback()
  541. wrap.MsgError(err.Error())
  542. return
  543. }
  544. if _, err = tx.Exec("DELETE FROM blog_posts WHERE id = ?;", pf_id); err != nil {
  545. tx.Rollback()
  546. wrap.MsgError(err.Error())
  547. return
  548. }
  549. // Commit all changes and unlock table
  550. err = tx.Commit()
  551. if err != nil {
  552. wrap.MsgError(err.Error())
  553. return
  554. }
  555. _, err = wrap.DB.Exec("UNLOCK TABLES;")
  556. if err != nil {
  557. wrap.MsgError(err.Error())
  558. return
  559. }
  560. // Reload current page
  561. wrap.Write(`window.location.reload(false);`)
  562. })
  563. }