diff --git a/.env.example b/.env.example index febc3fd..a967b50 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ ENV=prod +WAKAPI_DB_TYPE=mysql WAKAPI_DB_USER=myuser WAKAPI_DB_PASSWORD=mysecretpassword WAKAPI_DB_HOST=localhost diff --git a/README.md b/README.md index e5c305d..5479ae3 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ ## Prerequisites ### Server * Go >= 1.13 (with `$GOPATH` properly set) -* A MySQL database +* An SQL database (MySQL or Postgres) ### Client * [WakaTime plugin](https://wakatime.com/plugins) for your editor / IDE ## Usage -* Create an empty MySQL database +* Create an empty database * Enable Go module support: `export GO111MODULE=on` * Get code: `go get github.com/muety/wakapi` * Go to project root: `cd "$GOPATH/src/github.com/muety/wakapi"` @@ -36,6 +36,8 @@ * To get the api key look in the logs `docker-compose logs | grep "API key"` * The application should now be running on `localhost:3000` +### Build for specific platform +Example: `GOOS=darwin GOARCH=386 go build -o build/wakapi_1.1.0_$GOOS_$GOARCH github.com/muety/wakapi` ### User Accounts * When starting wakapi for the first time, a default user _**admin**_ with password _**admin**_ is created. The corresponding API key is printed to the console. @@ -64,12 +66,6 @@ For the above example, you would need to add two aliases, like this: It is recommended to use wakapi behind a **reverse proxy**, like [Caddy](https://caddyserver.com) or _nginx_ to enable **TLS encryption** (HTTPS). However, if you want to expose your wakapi instance to the public anyway, you need to set `listen = 0.0.0.0` in `config.ini` -## Todo -* User sign up and log in -* Additional endpoints for retrieving statistics data -* Support for SQLite database -* Unit tests - ## Important Note **This is not an alternative to using WakaTime.** It is just a custom, non-commercial, self-hosted application to collect coding statistics using the already existing editor plugins provided by the WakaTime community. It was created for personal use only and with the purpose of keeping the sovereignity of your own data. However, if you like the official product, **please support the authors and buy an official WakaTime subscription!** diff --git a/main.go b/main.go index 62e81c1..2565032 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( "github.com/n1try/wakapi/utils" _ "github.com/jinzhu/gorm/dialects/mysql" + _ "github.com/jinzhu/gorm/dialects/postgres" ) // TODO: Refactor entire project to be structured after business domains @@ -36,7 +37,9 @@ func readConfig() *models.Config { log.Fatal(err) } + // TODO: Use jinzhu/configor or so env, _ := os.LookupEnv("ENV") + dbType, valid := os.LookupEnv("WAKAPI_DB_TYPE") dbUser, valid := os.LookupEnv("WAKAPI_DB_USER") dbPassword, valid := os.LookupEnv("WAKAPI_DB_PASSWORD") dbHost, valid := os.LookupEnv("WAKAPI_DB_HOST") @@ -53,6 +56,10 @@ func readConfig() *models.Config { log.Fatalf("Fail to read file: %v", err) } + if dbType == "" { + dbType = "mysql" + } + dbMaxConn := cfg.Section("database").Key("max_connections").MustUint(1) addr := cfg.Section("server").Key("listen").MustString("127.0.0.1") port, err := strconv.Atoi(os.Getenv("PORT")) @@ -99,7 +106,7 @@ func readConfig() *models.Config { DbUser: dbUser, DbPassword: dbPassword, DbName: dbName, - DbDialect: "mysql", + DbDialect: dbType, DbMaxConn: dbMaxConn, CleanUp: cleanUp, CustomLanguages: customLangs, @@ -119,6 +126,7 @@ func main() { if err != nil { log.Fatal("Could not connect to database.") } + // TODO: Graceful shutdown defer db.Close() // Migrate database schema diff --git a/utils/common.go b/utils/common.go index 7a304fb..bda1246 100644 --- a/utils/common.go +++ b/utils/common.go @@ -2,9 +2,8 @@ package utils import ( "errors" + "fmt" "regexp" - "strconv" - "strings" "time" "github.com/n1try/wakapi/models" @@ -32,18 +31,34 @@ func ParseUserAgent(ua string) (string, string, error) { } func MakeConnectionString(config *models.Config) string { - location, _ := time.LoadLocation("Local") - str := strings.Builder{} - str.WriteString(config.DbUser) - str.WriteString(":") - str.WriteString(config.DbPassword) - str.WriteString("@tcp(") - str.WriteString(config.DbHost) - str.WriteString(":") - str.WriteString(strconv.Itoa(int(config.DbPort))) - str.WriteString(")/") - str.WriteString(config.DbName) - str.WriteString("?charset=utf8&parseTime=true&loc=") - str.WriteString(location.String()) - return str.String() + switch config.DbDialect { + case "mysql": + return mySqlConnectionString(config) + case "postgres": + return postgresConnectionString(config) + } + return "" +} + +func mySqlConnectionString(config *models.Config) string { + location, _ := time.LoadLocation("Local") + return fmt.Sprintf( + "%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=%s", + config.DbUser, + config.DbPassword, + config.DbHost, + config.DbPort, + config.DbName, + location.String(), + ) +} + +func postgresConnectionString(config *models.Config) string { + return fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=disable", + config.DbHost, + config.DbPort, + config.DbUser, + config.DbName, + config.DbPassword, + ) }