init.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. package app
  2. import (
  3. "database/sql"
  4. "devel.mephi.ru/dyokunev/cps-api/app/common"
  5. "devel.mephi.ru/dyokunev/cps-api/app/common/db"
  6. models "devel.mephi.ru/dyokunev/cps-models"
  7. acdir "devel.mephi.ru/dyokunev/go-acdir"
  8. sdModels11 "devel.mephi.ru/dyokunev/go-sd-models/api11"
  9. asuModels "devel.mephi.ru/dyokunev/go-asu-models"
  10. sdApi4 "devel.mephi.ru/dyokunev/go-sdapi/sdApi4"
  11. "encoding/json"
  12. "fmt"
  13. _ "github.com/go-sql-driver/mysql"
  14. _ "github.com/mattn/go-sqlite3"
  15. "github.com/revel/revel"
  16. "github.com/xaionaro/reform"
  17. "github.com/xaionaro/reform/dialects/mysql"
  18. "github.com/xaionaro/reform/dialects/sqlite3"
  19. "gopkg.in/cas.v2"
  20. "net/http"
  21. "net/url"
  22. "runtime"
  23. "strings"
  24. "time"
  25. )
  26. var (
  27. // AppVersion revel app version (ldflags)
  28. AppVersion string
  29. // BuildTime revel app build-time (ldflags)
  30. BuildTime string
  31. )
  32. var (
  33. casClient *cas.Client
  34. )
  35. const (
  36. SQL_LOGGER_TRACEBACK_DEPTH int = 10
  37. SQL_CHECK_TIMEOUT int64 = 5 * 1000 * 1000 * 1000 // 5 seconds
  38. )
  39. type smartLogger struct {
  40. dbName string
  41. traceLogger reform.Logger
  42. errorLogger reform.Logger
  43. traceEnable bool
  44. errorEnable bool
  45. }
  46. func (logger smartLogger) queryWrapper(query string) string {
  47. var where string
  48. for i := 2; i < 32; i++ {
  49. _, filePath, _, ok := runtime.Caller(i)
  50. if !ok {
  51. break
  52. }
  53. if strings.HasSuffix(filePath, "_reform.go") {
  54. continue
  55. }
  56. if strings.HasSuffix(filePath, "_common.go") {
  57. continue
  58. }
  59. if strings.HasSuffix(filePath, "querier.go") {
  60. continue
  61. }
  62. if strings.HasSuffix(filePath, "querier_selects.go") {
  63. continue
  64. }
  65. if strings.HasSuffix(filePath, "querier_commands.go") {
  66. continue
  67. }
  68. if filePath == `<autogenerated>` {
  69. continue
  70. }
  71. if strings.HasSuffix(filePath, "_generatedModelFunctions.go") {
  72. continue
  73. }
  74. if strings.HasSuffix(filePath, "_generatedMethods.go") {
  75. continue
  76. }
  77. whereArray := make([]string, SQL_LOGGER_TRACEBACK_DEPTH, SQL_LOGGER_TRACEBACK_DEPTH)
  78. for j := 0; j < SQL_LOGGER_TRACEBACK_DEPTH; j++ {
  79. _, filePath, line, ok := runtime.Caller(i + j)
  80. pathParts := strings.Split(filePath, "/")
  81. fileName := pathParts[len(pathParts)-1]
  82. if !ok || strings.HasSuffix(fileName, ".s") {
  83. whereArray = whereArray[SQL_LOGGER_TRACEBACK_DEPTH-j:]
  84. break
  85. }
  86. whereArray[SQL_LOGGER_TRACEBACK_DEPTH-1-j] = fmt.Sprintf("%v:%v", fileName, line)
  87. }
  88. where = "[" + strings.Join(whereArray, " -> ") + "] "
  89. break
  90. }
  91. return fmt.Sprintf("%v[db:%s] %s", where, logger.dbName, query)
  92. }
  93. func (logger *smartLogger) SetTraceEnable(enable bool) {
  94. logger.traceEnable = enable
  95. }
  96. func (logger *smartLogger) SetErrorEnable(enable bool) {
  97. logger.errorEnable = enable
  98. }
  99. func (logger smartLogger) Before(query string, args []interface{}) {
  100. if logger.traceEnable {
  101. logger.traceLogger.Before(logger.queryWrapper(query), args)
  102. }
  103. return
  104. }
  105. func (logger smartLogger) After(query string, args []interface{}, d time.Duration, err error) {
  106. if err != nil {
  107. if logger.errorEnable {
  108. logger.errorLogger.After(logger.queryWrapper(query), args, d, err)
  109. }
  110. } else {
  111. if logger.traceEnable {
  112. logger.traceLogger.After(logger.queryWrapper(query), args, d, err)
  113. }
  114. }
  115. return
  116. }
  117. func getMysqlConnectionParameters(dbname_cfg string) (host string, port string, user string, pass string, dbname string, protocol string, socket string) {
  118. host, _ = revel.Config.String("db_" + dbname_cfg + ".host")
  119. port, _ = revel.Config.String("db_" + dbname_cfg + ".port")
  120. user, _ = revel.Config.String("db_" + dbname_cfg + ".user")
  121. pass, _ = revel.Config.String("db_" + dbname_cfg + ".password")
  122. dbname, _ = revel.Config.String("db_" + dbname_cfg + ".name")
  123. protocol, _ = revel.Config.String("db_" + dbname_cfg + ".protocol")
  124. socket, _ = revel.Config.String("db_" + dbname_cfg + ".socket_path")
  125. return
  126. }
  127. func getMysqlConnectionString(dbname_cfg string) string {
  128. host, port, user, pass, dbname, protocol, socket := getMysqlConnectionParameters(dbname_cfg)
  129. switch protocol {
  130. case "tcp", "tcp6":
  131. return fmt.Sprintf("%s:%s@%s([%s]:%s)/%s%s?parseTime=true&readTimeout=30s", user, pass, protocol, host, port, dbname, "")
  132. case "unix":
  133. return fmt.Sprintf("%s:%s@%s(%s)/%s%s?parseTime=true&readTimeout=30s", user, pass, protocol, socket, dbname, "")
  134. default:
  135. revel.ERROR.Fatalf("This protocol is not supported: %v", protocol)
  136. }
  137. return ""
  138. }
  139. func setupDb(db *sql.DB, driver string) {
  140. switch driver {
  141. case "sqlite3":
  142. db.SetMaxIdleConns(1)
  143. db.SetMaxOpenConns(1)
  144. case "mysql":
  145. //db.Exec("SET wait_timeout=15")
  146. //db.Exec("SET interactive_timeout=15")
  147. db.SetMaxIdleConns(10)
  148. db.SetMaxOpenConns(100)
  149. break
  150. default:
  151. db.SetMaxIdleConns(10)
  152. db.SetMaxOpenConns(100)
  153. break
  154. }
  155. }
  156. func initReformDb(dbname string) (*reform.DB, *sql.DB) {
  157. revel.INFO.Printf("Init_reformDb(\"%v\")", dbname)
  158. var connectionString string
  159. driver, _ := revel.Config.String("db_" + dbname + ".driver")
  160. switch driver {
  161. case "mysql":
  162. connectionString = getMysqlConnectionString(dbname)
  163. case "sqlite3":
  164. connectionString, _ = revel.Config.String("db_" + dbname + ".path")
  165. connectionString += "?cache=shared&mode=rwc"
  166. default:
  167. revel.ERROR.Fatalf("Unknown driver: ", driver)
  168. }
  169. revel.INFO.Printf("sql.Open() connectionString for \"%v\" is \"%v\"", dbname, connectionString)
  170. db, err := sql.Open(driver, connectionString)
  171. if err != nil {
  172. revel.ERROR.Fatal(err)
  173. }
  174. setupDb(db, driver)
  175. //logger := reform.NewPrintfLogger(revel.TRACE.Printf)
  176. logger := smartLogger{dbName: dbname, traceLogger: reform.NewPrintfLogger(revel.TRACE.Printf), errorLogger: reform.NewPrintfLogger(revel.ERROR.Printf)}
  177. logger.SetTraceEnable(revel.DevMode)
  178. logger.SetErrorEnable(true)
  179. switch driver {
  180. case "mysql":
  181. return reform.NewDB(db, mysql.Dialect, logger), db
  182. case "sqlite3":
  183. return reform.NewDB(db, sqlite3.Dialect, logger), db
  184. default:
  185. revel.ERROR.Fatalf("Unknown driver: ", driver)
  186. }
  187. return nil, db
  188. }
  189. func preparePreCalculatedData() {
  190. tx, err := db.CpsRaw.Begin()
  191. if err != nil {
  192. revel.ERROR.Printf("Got error: %v", err.Error())
  193. return
  194. }
  195. if tx == nil {
  196. revel.ERROR.Printf("tx == nil")
  197. return
  198. }
  199. _, err = tx.Exec("insert ignore into activity_userids select distinct `UserId`, DATE_FORMAT(dtdate,'%Y-%m-01') from `events_view` where dtdate > FROM_UNIXTIME(UNIX_TIMESTAMP(NOW()) - 3600*24*90)")
  200. if err != nil {
  201. revel.ERROR.Printf("Got error: %v", err.Error())
  202. return
  203. }
  204. err = tx.Commit()
  205. if err != nil {
  206. revel.ERROR.Printf("Got error: %v", err.Error())
  207. return
  208. }
  209. }
  210. func initDB() {
  211. if db.Cps != nil {
  212. revel.ERROR.Panicf("DB is already initialized")
  213. return
  214. }
  215. db.Cps, db.CpsRaw = initReformDb("cps")
  216. models.EventSQL.SetDefaultDB(db.Cps)
  217. models.CpsUserSQL.SetDefaultDB(db.Cps)
  218. models.PassSQL.SetDefaultDB(db.Cps)
  219. models.FixOkRowSQL.SetDefaultDB(db.Cps)
  220. db.Sdapi1, db.Sdapi1Raw = initReformDb("sdapi1")
  221. db.Sdapi11, db.Sdapi11Raw = initReformDb("sdapi11")
  222. asuModels.FormularSQL.SetDefaultDB(db.Sdapi1)
  223. asuModels.UnitSQL.SetDefaultDB(db.Sdapi1)
  224. asuModels.PersonSQL.SetDefaultDB(db.Sdapi1)
  225. sdModels11.PersonSQL.SetDefaultDB(db.Sdapi11)
  226. asuModels.TimesheetRowSQL.SetDefaultDB(db.Sdapi1)
  227. if revel.DevMode {
  228. go preparePreCalculatedData()
  229. } else {
  230. preparePreCalculatedData()
  231. }
  232. go func() {
  233. for {
  234. time.Sleep(time.Hour)
  235. preparePreCalculatedData()
  236. }
  237. }()
  238. }
  239. /*func waitForDB() {
  240. if (dbCps == nil) {
  241. initDB()
  242. }
  243. startedToWaitAt := time.Now().UnixNano()
  244. _, err := dbCps.Exec("SELECT 1")
  245. for err != nil && time.Now().UnixNano() - startedToWaitAt < SQL_CHECK_TIMEOUT {
  246. time.Sleep(time.Nanosecond * 100 * 1000 * 1000)
  247. _, err = dbCps.Exec("SELECT 1")
  248. }
  249. if err != nil {
  250. revel.ERROR.Panicf("SQL connection is bad: %v", err)
  251. }
  252. }*/
  253. func initCasClient() {
  254. url, _ := url.Parse("https://login.mephi.ru")
  255. casClient = cas.NewClient(&cas.Options{
  256. URL: url,
  257. })
  258. }
  259. func initSdApi() {
  260. key, _ := revel.Config.String("sdapi4.apiKey")
  261. sdApi4.SetApiKey(key)
  262. }
  263. func initAcdir() {
  264. key, _ := revel.Config.String("sdapi_acdir_key")
  265. acdir.Init(key)
  266. }
  267. func initUserInfo() {
  268. common.InitUserInfo()
  269. }
  270. func init() {
  271. // Filters is the default set of global filters.
  272. revel.Filters = []revel.Filter{
  273. revel.PanicFilter, // Recover from panics and display an error page instead.
  274. revel.RouterFilter, // Use the routing table to select the right Action
  275. revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
  276. revel.ParamsFilter, // Parse parameters into Controller.Params.
  277. revel.SessionFilter, // Restore and write the session cookie.
  278. revel.FlashFilter, // Restore and write the flash cookie.
  279. revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
  280. revel.I18nFilter, // Resolve the requested language
  281. HeaderFilter, // Add some security based headers
  282. revel.InterceptorFilter, // Run interceptors around the action.
  283. revel.CompressFilter, // Compress the result.
  284. ActionInvoker, // Invoke the action.
  285. }
  286. // register startup functions with OnAppStart
  287. // revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
  288. // ( order dependent )
  289. // revel.OnAppStart(ExampleStartupScript)
  290. revel.OnAppStart(initDB)
  291. revel.OnAppStart(initCasClient)
  292. revel.OnAppStart(initAcdir)
  293. revel.OnAppStart(initUserInfo)
  294. revel.OnAppStart(initSdApi)
  295. }
  296. // HeaderFilter adds common security headers
  297. // TODO turn this into revel.HeaderFilter
  298. // should probably also have a filter for CSRF
  299. // not sure if it can go in the same filter or not
  300. var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
  301. c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
  302. c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
  303. c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
  304. fc[0](c, fc[1:]) // Execute the next filter stage.
  305. }
  306. func checkAuth(c *revel.Controller) (result bool, userInfo common.UserInfo) {
  307. //w, r := c.Response.Out, c.Request.Request
  308. /* s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
  309. if len(s) != 2 {
  310. return
  311. }
  312. b, err := base64.StdEncoding.DecodeString(s[1])
  313. if err != nil {
  314. return
  315. }
  316. pair := strings.SplitN(string(b), ":", 2)
  317. if len(pair) != 2 {
  318. return
  319. }
  320. if pair[0] == "" || pair[1] == "" {
  321. return
  322. }
  323. // You can add here internal authentication
  324. */
  325. var apiKey string
  326. c.Params.Bind(&apiKey, "apiKey")
  327. if apiKey == "" {
  328. return
  329. }
  330. fixokApiKey, ok := revel.Config.String("apiKey.fixOk")
  331. if ok {
  332. if apiKey == fixokApiKey {
  333. result = true
  334. userInfo.Permissions.IsFixOk = true
  335. userInfo.Permissions.IsFullAccess = true
  336. return
  337. }
  338. }
  339. voipApiKey, ok := revel.Config.String("apiKey.voip")
  340. if ok {
  341. if apiKey == voipApiKey {
  342. result = true
  343. userInfo.Permissions.IsVoip = true
  344. return
  345. }
  346. }
  347. dyokunevTestsApiKey, ok := revel.Config.String("apiKey.dyokunevTests")
  348. if ok {
  349. if apiKey == dyokunevTestsApiKey {
  350. result = true
  351. userInfo.Permissions.IsFixOk = true
  352. userInfo.Permissions.IsFullAccess = true
  353. return
  354. }
  355. }
  356. return
  357. }
  358. var ActionInvoker = func(c *revel.Controller, f []revel.Filter) {
  359. var username string
  360. if strings.HasPrefix(c.Request.Request.RequestURI, "/redirectTo/") {
  361. revel.ActionInvoker(c, f)
  362. return
  363. }
  364. isAuthed, userInfo := checkAuth(c)
  365. if !isAuthed {
  366. h := func(w http.ResponseWriter, r *http.Request) {
  367. if !cas.IsAuthenticated(r) {
  368. if strings.HasPrefix(c.Request.Request.RequestURI, "/login?back_url=http") {
  369. casClient.RedirectToLogin(w, r)
  370. }
  371. data := map[string]interface{}{
  372. "status": "ERROR",
  373. "error_action": "auth",
  374. "error_description": "Please authenticate on login.mephi.ru, first",
  375. }
  376. w.Header().Set("Content-Type", "application/json")
  377. json.NewEncoder(w).Encode(data)
  378. return
  379. }
  380. isAuthed = true
  381. username = cas.Username(r)
  382. }
  383. casClient.HandleFunc(h).ServeHTTP(c.Response.Out, c.Request.Request)
  384. }
  385. if !isAuthed {
  386. return
  387. }
  388. if username != "" {
  389. userInfo = common.GetUserInfo(username)
  390. }
  391. c.Args["me"] = userInfo
  392. c.ViewArgs["username"] = userInfo.Username
  393. revel.ActionInvoker(c, f)
  394. }
  395. //func ExampleStartupScript() {
  396. // // revel.DevMod and revel.RunMode work here
  397. // // Use this script to check for dev mode and set dev/prod startup scripts here!
  398. // if revel.DevMode == true {
  399. // // Dev mode
  400. // }
  401. //}