wrapper.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. package wrapper
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "html/template"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "golang-fave/engine/basket"
  14. "golang-fave/engine/cblocks"
  15. "golang-fave/engine/config"
  16. "golang-fave/engine/consts"
  17. "golang-fave/engine/logger"
  18. "golang-fave/engine/mysqlpool"
  19. "golang-fave/engine/sqlw"
  20. "golang-fave/engine/utils"
  21. "github.com/vladimirok5959/golang-server-sessions/session"
  22. )
  23. type Tx = sqlw.Tx
  24. var ErrNoRows = sqlw.ErrNoRows
  25. type Wrapper struct {
  26. l *logger.Logger
  27. W http.ResponseWriter
  28. R *http.Request
  29. S *session.Session
  30. c *cblocks.CacheBlocks
  31. Host string
  32. Port string
  33. CurrHost string
  34. DConfig string
  35. DHtdocs string
  36. DLogs string
  37. DTemplate string
  38. DTmp string
  39. IsBackend bool
  40. ConfMysqlExists bool
  41. UrlArgs []string
  42. CurrModule string
  43. CurrSubModule string
  44. MSPool *mysqlpool.MySqlPool
  45. ShopBasket *basket.Basket
  46. Config *config.Config
  47. DB *sqlw.DB
  48. User *utils.MySql_user
  49. ShopAllCurrencies *map[int]utils.MySql_shop_currency
  50. }
  51. func New(l *logger.Logger, w http.ResponseWriter, r *http.Request, s *session.Session, c *cblocks.CacheBlocks, host, port, chost, dirConfig, dirHtdocs, dirLogs, dirTemplate, dirTmp string, mp *mysqlpool.MySqlPool, sb *basket.Basket) *Wrapper {
  52. conf := config.ConfigNew()
  53. if err := conf.ConfigRead(dirConfig + string(os.PathSeparator) + "config.json"); err != nil {
  54. l.Log("Host config file: %s", r, true, err.Error())
  55. }
  56. return &Wrapper{
  57. l: l,
  58. W: w,
  59. R: r,
  60. S: s,
  61. c: c,
  62. Host: host,
  63. Port: port,
  64. CurrHost: chost,
  65. DConfig: dirConfig,
  66. DHtdocs: dirHtdocs,
  67. DLogs: dirLogs,
  68. DTemplate: dirTemplate,
  69. DTmp: dirTmp,
  70. UrlArgs: []string{},
  71. CurrModule: "",
  72. CurrSubModule: "",
  73. MSPool: mp,
  74. ShopBasket: sb,
  75. Config: conf,
  76. }
  77. }
  78. func (this *Wrapper) LogAccess(msg string, vars ...interface{}) {
  79. this.l.Log(msg, this.R, false, vars...)
  80. }
  81. func (this *Wrapper) LogError(msg string, vars ...interface{}) {
  82. this.l.Log(msg, this.R, true, vars...)
  83. }
  84. func (this *Wrapper) LogCpError(err *error) *error {
  85. if *err != nil {
  86. this.LogError("%s", *err)
  87. }
  88. return err
  89. }
  90. func (this *Wrapper) dbReconnect() error {
  91. if !utils.IsMySqlConfigExists(this.DConfig + string(os.PathSeparator) + "mysql.json") {
  92. return errors.New("can't read database configuration file")
  93. }
  94. mc, err := utils.MySqlConfigRead(this.DConfig + string(os.PathSeparator) + "mysql.json")
  95. if err != nil {
  96. return err
  97. }
  98. this.DB, err = sqlw.Open("mysql", mc.User+":"+mc.Password+"@tcp("+mc.Host+":"+mc.Port+")/"+mc.Name)
  99. if err != nil {
  100. return err
  101. }
  102. // Max 60 minutes and max 8 connection per host
  103. this.DB.SetConnMaxLifetime(time.Minute * 60)
  104. this.DB.SetMaxIdleConns(8)
  105. this.DB.SetMaxOpenConns(8)
  106. this.MSPool.Set(this.CurrHost, this.DB)
  107. return nil
  108. }
  109. func (this *Wrapper) UseDatabase() error {
  110. this.DB = this.MSPool.Get(this.CurrHost)
  111. if this.DB == nil {
  112. if err := this.dbReconnect(); err != nil {
  113. return err
  114. }
  115. }
  116. if err := this.DB.Ping(this.R.Context()); err != nil {
  117. this.DB.Close()
  118. if err := this.dbReconnect(); err != nil {
  119. return err
  120. } else {
  121. if err := this.DB.Ping(this.R.Context()); err != nil {
  122. this.DB.Close()
  123. this.MSPool.Del(this.CurrHost)
  124. return err
  125. }
  126. }
  127. }
  128. return nil
  129. }
  130. func (this *Wrapper) LoadSessionUser() bool {
  131. if this.S.GetInt("UserId", 0) <= 0 {
  132. return false
  133. }
  134. if this.DB == nil {
  135. return false
  136. }
  137. user := &utils.MySql_user{}
  138. err := this.DB.QueryRow(
  139. this.R.Context(),
  140. `SELECT
  141. id,
  142. first_name,
  143. last_name,
  144. email,
  145. password,
  146. admin,
  147. active
  148. FROM
  149. fave_users
  150. WHERE
  151. id = ?
  152. LIMIT 1;`,
  153. this.S.GetInt("UserId", 0),
  154. ).Scan(
  155. &user.A_id,
  156. &user.A_first_name,
  157. &user.A_last_name,
  158. &user.A_email,
  159. &user.A_password,
  160. &user.A_admin,
  161. &user.A_active,
  162. )
  163. if *this.LogCpError(&err) != nil {
  164. return false
  165. }
  166. if user.A_id != this.S.GetInt("UserId", 0) {
  167. return false
  168. }
  169. this.User = user
  170. return true
  171. }
  172. func (this *Wrapper) Write(data string) {
  173. // TODO: select context and don't write
  174. this.W.Write([]byte(data))
  175. }
  176. func (this *Wrapper) MsgSuccess(msg string) {
  177. // TODO: select context and don't write
  178. this.Write(fmt.Sprintf(
  179. `fave.ShowMsgSuccess('Success!', '%s', false);`,
  180. utils.JavaScriptVarValue(msg)))
  181. }
  182. func (this *Wrapper) MsgError(msg string) {
  183. // TODO: select context and don't write
  184. this.Write(fmt.Sprintf(
  185. `fave.ShowMsgError('Error!', '%s', true);`,
  186. utils.JavaScriptVarValue(msg)))
  187. }
  188. func (this *Wrapper) RenderToString(tcont []byte, data interface{}) string {
  189. tmpl, err := template.New("template").Parse(string(tcont))
  190. if err != nil {
  191. return err.Error()
  192. }
  193. var tpl bytes.Buffer
  194. if err := tmpl.Execute(&tpl, data); err != nil {
  195. return err.Error()
  196. }
  197. return tpl.String()
  198. }
  199. func (this *Wrapper) RenderFrontEnd(tname string, data interface{}, status int) {
  200. templates := []string{
  201. this.DTemplate + string(os.PathSeparator) + tname + ".html",
  202. this.DTemplate + string(os.PathSeparator) + "header.html",
  203. this.DTemplate + string(os.PathSeparator) + "sidebar-left.html",
  204. this.DTemplate + string(os.PathSeparator) + "sidebar-right.html",
  205. this.DTemplate + string(os.PathSeparator) + "footer.html",
  206. }
  207. tmpl, err := template.New(tname + ".html").Funcs(utils.TemplateAdditionalFuncs()).ParseFiles(templates...)
  208. if err != nil {
  209. utils.SystemErrorPageTemplate(this.W, err)
  210. return
  211. }
  212. var tpl bytes.Buffer
  213. err = tmpl.Execute(&tpl, consts.TmplData{
  214. System: utils.GetTmplSystemData("", ""),
  215. Data: data,
  216. })
  217. if err != nil {
  218. utils.SystemErrorPageTemplate(this.W, err)
  219. return
  220. }
  221. this.W.WriteHeader(status)
  222. this.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  223. this.W.Header().Set("Content-Type", "text/html; charset=utf-8")
  224. this.W.Write(tpl.Bytes())
  225. }
  226. func (this *Wrapper) RenderBackEnd(tcont []byte, data interface{}) {
  227. tmpl, err := template.New("template").Parse(string(tcont))
  228. if err != nil {
  229. utils.SystemErrorPageEngine(this.W, err)
  230. return
  231. }
  232. this.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  233. this.W.Header().Set("Content-Type", "text/html; charset=utf-8")
  234. var tpl bytes.Buffer
  235. err = tmpl.Execute(this.W, consts.TmplData{
  236. System: utils.GetTmplSystemData(this.CurrModule, this.CurrSubModule),
  237. Data: data,
  238. })
  239. if err != nil {
  240. utils.SystemErrorPageEngine(this.W, err)
  241. return
  242. }
  243. this.W.Write(tpl.Bytes())
  244. }
  245. func (this *Wrapper) GetCurrentPage(max int) int {
  246. curr := 1
  247. page := this.R.URL.Query().Get("p")
  248. if page != "" {
  249. if i, err := strconv.Atoi(page); err == nil {
  250. if i < 1 {
  251. curr = 1
  252. } else if i > max {
  253. curr = max
  254. } else {
  255. curr = i
  256. }
  257. }
  258. }
  259. return curr
  260. }
  261. func (this *Wrapper) ConfigSave() error {
  262. return this.Config.ConfigWrite(this.DConfig + string(os.PathSeparator) + "config.json")
  263. }
  264. func (this *Wrapper) SendEmailUsual(email, subject, message string) error {
  265. if (*this.Config).SMTP.Host == "" || (*this.Config).SMTP.Login == "" || (*this.Config).SMTP.Password == "" {
  266. return errors.New("SMTP server is not configured")
  267. }
  268. err := utils.SMTPSend(
  269. (*this.Config).SMTP.Host,
  270. utils.IntToStr((*this.Config).SMTP.Port),
  271. (*this.Config).SMTP.Login,
  272. (*this.Config).SMTP.Password,
  273. subject,
  274. message,
  275. []string{email},
  276. )
  277. status := 1
  278. emessage := ""
  279. if err != nil {
  280. status = 0
  281. emessage = err.Error()
  282. }
  283. if _, err := this.DB.Exec(
  284. this.R.Context(),
  285. `INSERT INTO fave_notify_mail SET
  286. id = NULL,
  287. email = ?,
  288. subject = ?,
  289. message = ?,
  290. error = ?,
  291. datetime = ?,
  292. status = ?
  293. ;`,
  294. email,
  295. subject,
  296. message,
  297. emessage,
  298. utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
  299. status,
  300. ); err != nil {
  301. this.LogCpError(&err)
  302. }
  303. return err
  304. }
  305. func (this *Wrapper) SendEmailFast(email, subject, message string) error {
  306. status := 2
  307. emessage := ""
  308. if (*this.Config).SMTP.Host == "" || (*this.Config).SMTP.Login == "" || (*this.Config).SMTP.Password == "" {
  309. status = 0
  310. emessage = "SMTP server is not configured"
  311. }
  312. if _, err := this.DB.Exec(
  313. this.R.Context(),
  314. `INSERT INTO fave_notify_mail SET
  315. id = NULL,
  316. email = ?,
  317. subject = ?,
  318. message = ?,
  319. error = ?,
  320. datetime = ?,
  321. status = ?
  322. ;`,
  323. email,
  324. subject,
  325. message,
  326. emessage,
  327. utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
  328. status,
  329. ); err != nil {
  330. return err
  331. }
  332. return nil
  333. }
  334. func (this *Wrapper) SendEmailTemplated(email, subject, tname string, data interface{}) error {
  335. tmpl, err := template.New(tname + ".html").Funcs(utils.TemplateAdditionalFuncs()).ParseFiles(
  336. this.DTemplate + string(os.PathSeparator) + tname + ".html",
  337. )
  338. if err != nil {
  339. return err
  340. }
  341. var tpl bytes.Buffer
  342. err = tmpl.Execute(&tpl, data)
  343. if err != nil {
  344. return err
  345. }
  346. return this.SendEmailFast(email, subject, string(tpl.Bytes()))
  347. }
  348. func (this *Wrapper) GetSessionId() string {
  349. cookie, err := this.R.Cookie("session")
  350. if err == nil && len(cookie.Value) == 40 {
  351. return cookie.Value
  352. }
  353. return ""
  354. }
  355. func (this *Wrapper) RecreateProductXmlFile() error {
  356. trigger := strings.Join([]string{this.DTmp, "trigger.xml.run"}, string(os.PathSeparator))
  357. if !utils.IsFileExists(trigger) {
  358. if _, err := os.Create(trigger); err != nil {
  359. return err
  360. }
  361. }
  362. return nil
  363. }
  364. func (this *Wrapper) RecreateProductImgFiles() error {
  365. trigger := strings.Join([]string{this.DTmp, "trigger.img.run"}, string(os.PathSeparator))
  366. if !utils.IsFileExists(trigger) {
  367. if _, err := os.Create(trigger); err != nil {
  368. return err
  369. }
  370. }
  371. return nil
  372. }
  373. func (this *Wrapper) RemoveProductImageThumbnails(product_id, filename string) error {
  374. pattern := this.DHtdocs + string(os.PathSeparator) + strings.Join([]string{"products", "images", product_id, filename}, string(os.PathSeparator))
  375. if files, err := filepath.Glob(pattern); err != nil {
  376. return err
  377. } else {
  378. // TODO: select context and don't do that
  379. for _, file := range files {
  380. if err := os.Remove(file); err != nil {
  381. return errors.New(fmt.Sprintf("[upload delete] Thumbnail file (%s) delete error: %s", file, err.Error()))
  382. }
  383. }
  384. }
  385. return this.RecreateProductImgFiles()
  386. }
  387. func (this *Wrapper) ShopGetAllCurrencies() *map[int]utils.MySql_shop_currency {
  388. if this.ShopAllCurrencies == nil {
  389. this.ShopAllCurrencies = &map[int]utils.MySql_shop_currency{}
  390. if rows, err := this.DB.Query(
  391. this.R.Context(),
  392. `SELECT
  393. id,
  394. name,
  395. coefficient,
  396. code,
  397. symbol
  398. FROM
  399. fave_shop_currencies
  400. ORDER BY
  401. id ASC
  402. ;`,
  403. ); err == nil {
  404. defer rows.Close()
  405. for rows.Next() {
  406. row := utils.MySql_shop_currency{}
  407. if err = rows.Scan(
  408. &row.A_id,
  409. &row.A_name,
  410. &row.A_coefficient,
  411. &row.A_code,
  412. &row.A_symbol,
  413. ); err == nil {
  414. (*this.ShopAllCurrencies)[row.A_id] = row
  415. }
  416. }
  417. }
  418. }
  419. return this.ShopAllCurrencies
  420. }
  421. func (this *Wrapper) ShopGetCurrentCurrency() *utils.MySql_shop_currency {
  422. currency_id := 1
  423. if cookie, err := this.R.Cookie("currency"); err == nil {
  424. currency_id = utils.StrToInt(cookie.Value)
  425. }
  426. if _, ok := (*this.ShopGetAllCurrencies())[currency_id]; ok != true {
  427. currency_id = 1
  428. }
  429. if p, ok := (*this.ShopGetAllCurrencies())[currency_id]; ok == true {
  430. return &p
  431. }
  432. return nil
  433. }
  434. func (this *Wrapper) IsSystemMountedTemplateFile(filename string) bool {
  435. return utils.InArrayString([]string{
  436. "404.html",
  437. "blog-category.html",
  438. "blog-post.html",
  439. "blog.html",
  440. "cached-block-1.html",
  441. "cached-block-2.html",
  442. "cached-block-3.html",
  443. "cached-block-4.html",
  444. "cached-block-5.html",
  445. "email-new-order-admin.html",
  446. "email-new-order-user.html",
  447. "footer.html",
  448. "header.html",
  449. "index.html",
  450. "maintenance.html",
  451. "page.html",
  452. "robots.txt",
  453. "scripts.js",
  454. "shop-category.html",
  455. "shop-product.html",
  456. "shop.html",
  457. "sidebar-left.html",
  458. "sidebar-right.html",
  459. "styles.css",
  460. }, filename)
  461. }