wrapper.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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/cblocks"
  14. "golang-fave/consts"
  15. "golang-fave/engine/basket"
  16. "golang-fave/engine/mysqlpool"
  17. "golang-fave/engine/sqlw"
  18. "golang-fave/engine/wrapper/config"
  19. "golang-fave/logger"
  20. "golang-fave/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. this.MSPool.Set(this.CurrHost, this.DB)
  103. return nil
  104. }
  105. func (this *Wrapper) UseDatabase() error {
  106. this.DB = this.MSPool.Get(this.CurrHost)
  107. if this.DB == nil {
  108. if err := this.dbReconnect(); err != nil {
  109. return err
  110. }
  111. }
  112. if err := this.DB.Ping(this.R.Context()); err != nil {
  113. this.DB.Close()
  114. if err := this.dbReconnect(); err != nil {
  115. return err
  116. }
  117. if err := this.DB.Ping(this.R.Context()); err != nil {
  118. this.DB.Close()
  119. return err
  120. }
  121. }
  122. // Max 60 minutes and max 4 connection per host
  123. this.DB.SetConnMaxLifetime(time.Minute * 60)
  124. this.DB.SetMaxIdleConns(4)
  125. this.DB.SetMaxOpenConns(4)
  126. return nil
  127. }
  128. func (this *Wrapper) LoadSessionUser() bool {
  129. if this.S.GetInt("UserId", 0) <= 0 {
  130. return false
  131. }
  132. if this.DB == nil {
  133. return false
  134. }
  135. user := &utils.MySql_user{}
  136. err := this.DB.QueryRow(
  137. this.R.Context(),
  138. `SELECT
  139. id,
  140. first_name,
  141. last_name,
  142. email,
  143. password,
  144. admin,
  145. active
  146. FROM
  147. users
  148. WHERE
  149. id = ?
  150. LIMIT 1;`,
  151. this.S.GetInt("UserId", 0),
  152. ).Scan(
  153. &user.A_id,
  154. &user.A_first_name,
  155. &user.A_last_name,
  156. &user.A_email,
  157. &user.A_password,
  158. &user.A_admin,
  159. &user.A_active,
  160. )
  161. if *this.LogCpError(&err) != nil {
  162. return false
  163. }
  164. if user.A_id != this.S.GetInt("UserId", 0) {
  165. return false
  166. }
  167. this.User = user
  168. return true
  169. }
  170. func (this *Wrapper) Write(data string) {
  171. // TODO: select context and don't write
  172. this.W.Write([]byte(data))
  173. }
  174. func (this *Wrapper) MsgSuccess(msg string) {
  175. // TODO: select context and don't write
  176. this.Write(fmt.Sprintf(
  177. `fave.ShowMsgSuccess('Success!', '%s', false);`,
  178. utils.JavaScriptVarValue(msg)))
  179. }
  180. func (this *Wrapper) MsgError(msg string) {
  181. // TODO: select context and don't write
  182. this.Write(fmt.Sprintf(
  183. `fave.ShowMsgError('Error!', '%s', true);`,
  184. utils.JavaScriptVarValue(msg)))
  185. }
  186. func (this *Wrapper) RenderToString(tcont []byte, data interface{}) string {
  187. tmpl, err := template.New("template").Parse(string(tcont))
  188. if err != nil {
  189. return err.Error()
  190. }
  191. var tpl bytes.Buffer
  192. if err := tmpl.Execute(&tpl, data); err != nil {
  193. return err.Error()
  194. }
  195. return tpl.String()
  196. }
  197. func (this *Wrapper) RenderFrontEnd(tname string, data interface{}, status int) {
  198. tmpl, err := template.New(tname+".html").Funcs(utils.TemplateAdditionalFuncs()).ParseFiles(
  199. this.DTemplate+string(os.PathSeparator)+tname+".html",
  200. this.DTemplate+string(os.PathSeparator)+"header.html",
  201. this.DTemplate+string(os.PathSeparator)+"sidebar-left.html",
  202. this.DTemplate+string(os.PathSeparator)+"sidebar-right.html",
  203. this.DTemplate+string(os.PathSeparator)+"footer.html",
  204. )
  205. if err != nil {
  206. utils.SystemErrorPageTemplate(this.W, err)
  207. return
  208. }
  209. var tpl bytes.Buffer
  210. err = tmpl.Execute(&tpl, consts.TmplData{
  211. System: utils.GetTmplSystemData("", ""),
  212. Data: data,
  213. })
  214. if err != nil {
  215. utils.SystemErrorPageTemplate(this.W, err)
  216. return
  217. }
  218. this.W.WriteHeader(status)
  219. this.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  220. this.W.Header().Set("Content-Type", "text/html; charset=utf-8")
  221. this.W.Write(tpl.Bytes())
  222. }
  223. func (this *Wrapper) RenderBackEnd(tcont []byte, data interface{}) {
  224. tmpl, err := template.New("template").Parse(string(tcont))
  225. if err != nil {
  226. utils.SystemErrorPageEngine(this.W, err)
  227. return
  228. }
  229. this.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  230. this.W.Header().Set("Content-Type", "text/html; charset=utf-8")
  231. var tpl bytes.Buffer
  232. err = tmpl.Execute(this.W, consts.TmplData{
  233. System: utils.GetTmplSystemData(this.CurrModule, this.CurrSubModule),
  234. Data: data,
  235. })
  236. if err != nil {
  237. utils.SystemErrorPageEngine(this.W, err)
  238. return
  239. }
  240. this.W.Write(tpl.Bytes())
  241. }
  242. func (this *Wrapper) GetCurrentPage(max int) int {
  243. curr := 1
  244. page := this.R.URL.Query().Get("p")
  245. if page != "" {
  246. if i, err := strconv.Atoi(page); err == nil {
  247. if i < 1 {
  248. curr = 1
  249. } else if i > max {
  250. curr = max
  251. } else {
  252. curr = i
  253. }
  254. }
  255. }
  256. return curr
  257. }
  258. func (this *Wrapper) ConfigSave() error {
  259. return this.Config.ConfigWrite(this.DConfig + string(os.PathSeparator) + "config.json")
  260. }
  261. func (this *Wrapper) SendEmailUsual(email, subject, message string) error {
  262. if (*this.Config).SMTP.Host == "" || (*this.Config).SMTP.Login == "" || (*this.Config).SMTP.Password == "" {
  263. return errors.New("SMTP server is not configured")
  264. }
  265. err := utils.SMTPSend(
  266. (*this.Config).SMTP.Host,
  267. utils.IntToStr((*this.Config).SMTP.Port),
  268. (*this.Config).SMTP.Login,
  269. (*this.Config).SMTP.Password,
  270. subject,
  271. message,
  272. []string{email},
  273. )
  274. status := 1
  275. emessage := ""
  276. if err != nil {
  277. status = 0
  278. emessage = err.Error()
  279. }
  280. if _, err := this.DB.Exec(
  281. this.R.Context(),
  282. `INSERT INTO notify_mail SET
  283. id = NULL,
  284. email = ?,
  285. subject = ?,
  286. message = ?,
  287. error = ?,
  288. datetime = ?,
  289. status = ?
  290. ;`,
  291. email,
  292. subject,
  293. message,
  294. emessage,
  295. utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
  296. status,
  297. ); err != nil {
  298. this.LogCpError(&err)
  299. }
  300. return err
  301. }
  302. func (this *Wrapper) SendEmailFast(email, subject, message string) error {
  303. status := 2
  304. emessage := ""
  305. if (*this.Config).SMTP.Host == "" || (*this.Config).SMTP.Login == "" || (*this.Config).SMTP.Password == "" {
  306. status = 0
  307. emessage = "SMTP server is not configured"
  308. }
  309. if _, err := this.DB.Exec(
  310. this.R.Context(),
  311. `INSERT INTO notify_mail SET
  312. id = NULL,
  313. email = ?,
  314. subject = ?,
  315. message = ?,
  316. error = ?,
  317. datetime = ?,
  318. status = ?
  319. ;`,
  320. email,
  321. subject,
  322. message,
  323. emessage,
  324. utils.UnixTimestampToMySqlDateTime(utils.GetCurrentUnixTimestamp()),
  325. status,
  326. ); err != nil {
  327. return err
  328. }
  329. return nil
  330. }
  331. func (this *Wrapper) SendEmailTemplated(email, subject, tname string, data interface{}) error {
  332. tmpl, err := template.New(tname + ".html").Funcs(utils.TemplateAdditionalFuncs()).ParseFiles(
  333. this.DTemplate + string(os.PathSeparator) + tname + ".html",
  334. )
  335. if err != nil {
  336. return err
  337. }
  338. var tpl bytes.Buffer
  339. err = tmpl.Execute(&tpl, data)
  340. if err != nil {
  341. return err
  342. }
  343. return this.SendEmailFast(email, subject, string(tpl.Bytes()))
  344. }
  345. func (this *Wrapper) GetSessionId() string {
  346. cookie, err := this.R.Cookie("session")
  347. if err == nil && len(cookie.Value) == 40 {
  348. return cookie.Value
  349. }
  350. return ""
  351. }
  352. func (this *Wrapper) RecreateProductXmlFile() error {
  353. trigger := strings.Join([]string{this.DTmp, "trigger.xml.run"}, string(os.PathSeparator))
  354. if !utils.IsFileExists(trigger) {
  355. if _, err := os.Create(trigger); err != nil {
  356. return err
  357. }
  358. }
  359. return nil
  360. }
  361. func (this *Wrapper) RecreateProductImgFiles() error {
  362. trigger := strings.Join([]string{this.DTmp, "trigger.img.run"}, string(os.PathSeparator))
  363. if !utils.IsFileExists(trigger) {
  364. if _, err := os.Create(trigger); err != nil {
  365. return err
  366. }
  367. }
  368. return nil
  369. }
  370. func (this *Wrapper) RemoveProductImageThumbnails(product_id, filename string) error {
  371. pattern := this.DHtdocs + string(os.PathSeparator) + strings.Join([]string{"products", "images", product_id, filename}, string(os.PathSeparator))
  372. if files, err := filepath.Glob(pattern); err != nil {
  373. return err
  374. } else {
  375. // TODO: select context and don't do that
  376. for _, file := range files {
  377. if err := os.Remove(file); err != nil {
  378. return errors.New(fmt.Sprintf("[upload delete] Thumbnail file (%s) delete error: %s", file, err.Error()))
  379. }
  380. }
  381. }
  382. return this.RecreateProductImgFiles()
  383. }
  384. func (this *Wrapper) ShopGetAllCurrencies() *map[int]utils.MySql_shop_currency {
  385. if this.ShopAllCurrencies == nil {
  386. this.ShopAllCurrencies = &map[int]utils.MySql_shop_currency{}
  387. if rows, err := this.DB.Query(
  388. this.R.Context(),
  389. `SELECT
  390. id,
  391. name,
  392. coefficient,
  393. code,
  394. symbol
  395. FROM
  396. shop_currencies
  397. ORDER BY
  398. id ASC
  399. ;`,
  400. ); err == nil {
  401. defer rows.Close()
  402. for rows.Next() {
  403. row := utils.MySql_shop_currency{}
  404. if err = rows.Scan(
  405. &row.A_id,
  406. &row.A_name,
  407. &row.A_coefficient,
  408. &row.A_code,
  409. &row.A_symbol,
  410. ); err == nil {
  411. (*this.ShopAllCurrencies)[row.A_id] = row
  412. }
  413. }
  414. }
  415. }
  416. return this.ShopAllCurrencies
  417. }
  418. func (this *Wrapper) ShopGetCurrentCurrency() *utils.MySql_shop_currency {
  419. currency_id := 1
  420. if cookie, err := this.R.Cookie("currency"); err == nil {
  421. currency_id = utils.StrToInt(cookie.Value)
  422. }
  423. if _, ok := (*this.ShopGetAllCurrencies())[currency_id]; ok != true {
  424. currency_id = 1
  425. }
  426. if p, ok := (*this.ShopGetAllCurrencies())[currency_id]; ok == true {
  427. return &p
  428. }
  429. return nil
  430. }