123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- package app
- import (
- "database/sql"
- "devel.mephi.ru/dyokunev/cps-api/app/common"
- "devel.mephi.ru/dyokunev/cps-api/app/common/db"
- models "devel.mephi.ru/dyokunev/cps-models"
- acdir "devel.mephi.ru/dyokunev/go-acdir"
- sdModels11 "devel.mephi.ru/dyokunev/go-sd-models/api11"
- asuModels "devel.mephi.ru/dyokunev/go-asu-models"
- sdApi4 "devel.mephi.ru/dyokunev/go-sdapi/sdApi4"
- "encoding/json"
- "fmt"
- _ "github.com/go-sql-driver/mysql"
- _ "github.com/mattn/go-sqlite3"
- "github.com/revel/revel"
- "github.com/xaionaro/reform"
- "github.com/xaionaro/reform/dialects/mysql"
- "github.com/xaionaro/reform/dialects/sqlite3"
- "gopkg.in/cas.v2"
- "net/http"
- "net/url"
- "runtime"
- "strings"
- "time"
- )
- var (
- // AppVersion revel app version (ldflags)
- AppVersion string
- // BuildTime revel app build-time (ldflags)
- BuildTime string
- )
- var (
- casClient *cas.Client
- )
- const (
- SQL_LOGGER_TRACEBACK_DEPTH int = 10
- SQL_CHECK_TIMEOUT int64 = 5 * 1000 * 1000 * 1000 // 5 seconds
- )
- type smartLogger struct {
- dbName string
- traceLogger reform.Logger
- errorLogger reform.Logger
- traceEnable bool
- errorEnable bool
- }
- func (logger smartLogger) queryWrapper(query string) string {
- var where string
- for i := 2; i < 32; i++ {
- _, filePath, _, ok := runtime.Caller(i)
- if !ok {
- break
- }
- if strings.HasSuffix(filePath, "_reform.go") {
- continue
- }
- if strings.HasSuffix(filePath, "_common.go") {
- continue
- }
- if strings.HasSuffix(filePath, "querier.go") {
- continue
- }
- if strings.HasSuffix(filePath, "querier_selects.go") {
- continue
- }
- if strings.HasSuffix(filePath, "querier_commands.go") {
- continue
- }
- if filePath == `<autogenerated>` {
- continue
- }
- if strings.HasSuffix(filePath, "_generatedModelFunctions.go") {
- continue
- }
- if strings.HasSuffix(filePath, "_generatedMethods.go") {
- continue
- }
- whereArray := make([]string, SQL_LOGGER_TRACEBACK_DEPTH, SQL_LOGGER_TRACEBACK_DEPTH)
- for j := 0; j < SQL_LOGGER_TRACEBACK_DEPTH; j++ {
- _, filePath, line, ok := runtime.Caller(i + j)
- pathParts := strings.Split(filePath, "/")
- fileName := pathParts[len(pathParts)-1]
- if !ok || strings.HasSuffix(fileName, ".s") {
- whereArray = whereArray[SQL_LOGGER_TRACEBACK_DEPTH-j:]
- break
- }
- whereArray[SQL_LOGGER_TRACEBACK_DEPTH-1-j] = fmt.Sprintf("%v:%v", fileName, line)
- }
- where = "[" + strings.Join(whereArray, " -> ") + "] "
- break
- }
- return fmt.Sprintf("%v[db:%s] %s", where, logger.dbName, query)
- }
- func (logger *smartLogger) SetTraceEnable(enable bool) {
- logger.traceEnable = enable
- }
- func (logger *smartLogger) SetErrorEnable(enable bool) {
- logger.errorEnable = enable
- }
- func (logger smartLogger) Before(query string, args []interface{}) {
- if logger.traceEnable {
- logger.traceLogger.Before(logger.queryWrapper(query), args)
- }
- return
- }
- func (logger smartLogger) After(query string, args []interface{}, d time.Duration, err error) {
- if err != nil {
- if logger.errorEnable {
- logger.errorLogger.After(logger.queryWrapper(query), args, d, err)
- }
- } else {
- if logger.traceEnable {
- logger.traceLogger.After(logger.queryWrapper(query), args, d, err)
- }
- }
- return
- }
- func getMysqlConnectionParameters(dbname_cfg string) (host string, port string, user string, pass string, dbname string, protocol string, socket string) {
- host, _ = revel.Config.String("db_" + dbname_cfg + ".host")
- port, _ = revel.Config.String("db_" + dbname_cfg + ".port")
- user, _ = revel.Config.String("db_" + dbname_cfg + ".user")
- pass, _ = revel.Config.String("db_" + dbname_cfg + ".password")
- dbname, _ = revel.Config.String("db_" + dbname_cfg + ".name")
- protocol, _ = revel.Config.String("db_" + dbname_cfg + ".protocol")
- socket, _ = revel.Config.String("db_" + dbname_cfg + ".socket_path")
- return
- }
- func getMysqlConnectionString(dbname_cfg string) string {
- host, port, user, pass, dbname, protocol, socket := getMysqlConnectionParameters(dbname_cfg)
- switch protocol {
- case "tcp", "tcp6":
- return fmt.Sprintf("%s:%s@%s([%s]:%s)/%s%s?parseTime=true&readTimeout=30s", user, pass, protocol, host, port, dbname, "")
- case "unix":
- return fmt.Sprintf("%s:%s@%s(%s)/%s%s?parseTime=true&readTimeout=30s", user, pass, protocol, socket, dbname, "")
- default:
- revel.ERROR.Fatalf("This protocol is not supported: %v", protocol)
- }
- return ""
- }
- func setupDb(db *sql.DB, driver string) {
- switch driver {
- case "sqlite3":
- db.SetMaxIdleConns(1)
- db.SetMaxOpenConns(1)
- case "mysql":
- //db.Exec("SET wait_timeout=15")
- //db.Exec("SET interactive_timeout=15")
- db.SetMaxIdleConns(10)
- db.SetMaxOpenConns(100)
- break
- default:
- db.SetMaxIdleConns(10)
- db.SetMaxOpenConns(100)
- break
- }
- }
- func initReformDb(dbname string) (*reform.DB, *sql.DB) {
- revel.INFO.Printf("Init_reformDb(\"%v\")", dbname)
- var connectionString string
- driver, _ := revel.Config.String("db_" + dbname + ".driver")
- switch driver {
- case "mysql":
- connectionString = getMysqlConnectionString(dbname)
- case "sqlite3":
- connectionString, _ = revel.Config.String("db_" + dbname + ".path")
- connectionString += "?cache=shared&mode=rwc"
- default:
- revel.ERROR.Fatalf("Unknown driver: ", driver)
- }
- revel.INFO.Printf("sql.Open() connectionString for \"%v\" is \"%v\"", dbname, connectionString)
- db, err := sql.Open(driver, connectionString)
- if err != nil {
- revel.ERROR.Fatal(err)
- }
- setupDb(db, driver)
- //logger := reform.NewPrintfLogger(revel.TRACE.Printf)
- logger := smartLogger{dbName: dbname, traceLogger: reform.NewPrintfLogger(revel.TRACE.Printf), errorLogger: reform.NewPrintfLogger(revel.ERROR.Printf)}
- logger.SetTraceEnable(revel.DevMode)
- logger.SetErrorEnable(true)
- switch driver {
- case "mysql":
- return reform.NewDB(db, mysql.Dialect, logger), db
- case "sqlite3":
- return reform.NewDB(db, sqlite3.Dialect, logger), db
- default:
- revel.ERROR.Fatalf("Unknown driver: ", driver)
- }
- return nil, db
- }
- func preparePreCalculatedData() {
- tx, err := db.CpsRaw.Begin()
- if err != nil {
- revel.ERROR.Printf("Got error: %v", err.Error())
- return
- }
- if tx == nil {
- revel.ERROR.Printf("tx == nil")
- return
- }
- _, 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)")
- if err != nil {
- revel.ERROR.Printf("Got error: %v", err.Error())
- return
- }
- err = tx.Commit()
- if err != nil {
- revel.ERROR.Printf("Got error: %v", err.Error())
- return
- }
- }
- func initDB() {
- if db.Cps != nil {
- revel.ERROR.Panicf("DB is already initialized")
- return
- }
- db.Cps, db.CpsRaw = initReformDb("cps")
- models.EventSQL.SetDefaultDB(db.Cps)
- models.CpsUserSQL.SetDefaultDB(db.Cps)
- models.PassSQL.SetDefaultDB(db.Cps)
- models.FixOkRowSQL.SetDefaultDB(db.Cps)
- db.Sdapi1, db.Sdapi1Raw = initReformDb("sdapi1")
- db.Sdapi11, db.Sdapi11Raw = initReformDb("sdapi11")
- asuModels.FormularSQL.SetDefaultDB(db.Sdapi1)
- asuModels.UnitSQL.SetDefaultDB(db.Sdapi1)
- asuModels.PersonSQL.SetDefaultDB(db.Sdapi1)
- sdModels11.PersonSQL.SetDefaultDB(db.Sdapi11)
- asuModels.TimesheetRowSQL.SetDefaultDB(db.Sdapi1)
- if revel.DevMode {
- go preparePreCalculatedData()
- } else {
- preparePreCalculatedData()
- }
- go func() {
- for {
- time.Sleep(time.Hour)
- preparePreCalculatedData()
- }
- }()
- }
- /*func waitForDB() {
- if (dbCps == nil) {
- initDB()
- }
- startedToWaitAt := time.Now().UnixNano()
- _, err := dbCps.Exec("SELECT 1")
- for err != nil && time.Now().UnixNano() - startedToWaitAt < SQL_CHECK_TIMEOUT {
- time.Sleep(time.Nanosecond * 100 * 1000 * 1000)
- _, err = dbCps.Exec("SELECT 1")
- }
- if err != nil {
- revel.ERROR.Panicf("SQL connection is bad: %v", err)
- }
- }*/
- func initCasClient() {
- url, _ := url.Parse("https://login.mephi.ru")
- casClient = cas.NewClient(&cas.Options{
- URL: url,
- })
- }
- func initSdApi() {
- key, _ := revel.Config.String("sdapi4.apiKey")
- sdApi4.SetApiKey(key)
- }
- func initAcdir() {
- key, _ := revel.Config.String("sdapi_acdir_key")
- acdir.Init(key)
- }
- func initUserInfo() {
- common.InitUserInfo()
- }
- func init() {
- // Filters is the default set of global filters.
- revel.Filters = []revel.Filter{
- revel.PanicFilter, // Recover from panics and display an error page instead.
- revel.RouterFilter, // Use the routing table to select the right Action
- revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
- revel.ParamsFilter, // Parse parameters into Controller.Params.
- revel.SessionFilter, // Restore and write the session cookie.
- revel.FlashFilter, // Restore and write the flash cookie.
- revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
- revel.I18nFilter, // Resolve the requested language
- HeaderFilter, // Add some security based headers
- revel.InterceptorFilter, // Run interceptors around the action.
- revel.CompressFilter, // Compress the result.
- ActionInvoker, // Invoke the action.
- }
- // register startup functions with OnAppStart
- // revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
- // ( order dependent )
- // revel.OnAppStart(ExampleStartupScript)
- revel.OnAppStart(initDB)
- revel.OnAppStart(initCasClient)
- revel.OnAppStart(initAcdir)
- revel.OnAppStart(initUserInfo)
- revel.OnAppStart(initSdApi)
- }
- // HeaderFilter adds common security headers
- // TODO turn this into revel.HeaderFilter
- // should probably also have a filter for CSRF
- // not sure if it can go in the same filter or not
- var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
- c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
- c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
- c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
- fc[0](c, fc[1:]) // Execute the next filter stage.
- }
- func checkAuth(c *revel.Controller) (result bool, userInfo common.UserInfo) {
- //w, r := c.Response.Out, c.Request.Request
- /* s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
- if len(s) != 2 {
- return
- }
- b, err := base64.StdEncoding.DecodeString(s[1])
- if err != nil {
- return
- }
- pair := strings.SplitN(string(b), ":", 2)
- if len(pair) != 2 {
- return
- }
- if pair[0] == "" || pair[1] == "" {
- return
- }
- // You can add here internal authentication
- */
- var apiKey string
- c.Params.Bind(&apiKey, "apiKey")
- if apiKey == "" {
- return
- }
- fixokApiKey, ok := revel.Config.String("apiKey.fixOk")
- if ok {
- if apiKey == fixokApiKey {
- result = true
- userInfo.Permissions.IsFixOk = true
- userInfo.Permissions.IsFullAccess = true
- return
- }
- }
- voipApiKey, ok := revel.Config.String("apiKey.voip")
- if ok {
- if apiKey == voipApiKey {
- result = true
- userInfo.Permissions.IsVoip = true
- return
- }
- }
- dyokunevTestsApiKey, ok := revel.Config.String("apiKey.dyokunevTests")
- if ok {
- if apiKey == dyokunevTestsApiKey {
- result = true
- userInfo.Permissions.IsFixOk = true
- userInfo.Permissions.IsFullAccess = true
- return
- }
- }
- return
- }
- var ActionInvoker = func(c *revel.Controller, f []revel.Filter) {
- var username string
- if strings.HasPrefix(c.Request.Request.RequestURI, "/redirectTo/") {
- revel.ActionInvoker(c, f)
- return
- }
- isAuthed, userInfo := checkAuth(c)
- if !isAuthed {
- h := func(w http.ResponseWriter, r *http.Request) {
- if !cas.IsAuthenticated(r) {
- if strings.HasPrefix(c.Request.Request.RequestURI, "/login?back_url=http") {
- casClient.RedirectToLogin(w, r)
- }
- data := map[string]interface{}{
- "status": "ERROR",
- "error_action": "auth",
- "error_description": "Please authenticate on login.mephi.ru, first",
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(data)
- return
- }
- isAuthed = true
- username = cas.Username(r)
- }
- casClient.HandleFunc(h).ServeHTTP(c.Response.Out, c.Request.Request)
- }
- if !isAuthed {
- return
- }
- if username != "" {
- userInfo = common.GetUserInfo(username)
- }
- c.Args["me"] = userInfo
- c.ViewArgs["username"] = userInfo.Username
- revel.ActionInvoker(c, f)
- }
- //func ExampleStartupScript() {
- // // revel.DevMod and revel.RunMode work here
- // // Use this script to check for dev mode and set dev/prod startup scripts here!
- // if revel.DevMode == true {
- // // Dev mode
- // }
- //}
|