2020-06-07 20:28:32 +03:00
package routes
import (
2021-01-30 12:54:54 +03:00
"encoding/base64"
2020-06-07 20:28:32 +03:00
"fmt"
2021-01-30 13:17:37 +03:00
"github.com/emvi/logbuch"
2021-01-30 12:34:52 +03:00
"github.com/gorilla/mux"
2020-06-07 20:28:32 +03:00
"github.com/gorilla/schema"
2020-10-09 22:37:16 +03:00
conf "github.com/muety/wakapi/config"
2021-02-03 23:28:02 +03:00
"github.com/muety/wakapi/middlewares"
2020-06-07 20:28:32 +03:00
"github.com/muety/wakapi/models"
2020-11-06 23:19:54 +03:00
"github.com/muety/wakapi/models/view"
2022-01-13 19:10:24 +03:00
routeutils "github.com/muety/wakapi/routes/utils"
2020-06-07 20:28:32 +03:00
"github.com/muety/wakapi/services"
2021-02-05 20:47:28 +03:00
"github.com/muety/wakapi/services/imports"
2020-06-07 20:28:32 +03:00
"github.com/muety/wakapi/utils"
"net/http"
2021-06-12 11:44:19 +03:00
"sort"
2020-10-27 00:34:50 +03:00
"strconv"
2021-06-12 11:44:19 +03:00
"strings"
2021-01-30 12:54:54 +03:00
"time"
2020-06-07 20:28:32 +03:00
)
2021-06-12 11:44:19 +03:00
const criticalError = "a critical error has occurred, sorry"
2020-06-07 20:28:32 +03:00
type SettingsHandler struct {
2020-11-01 22:14:10 +03:00
config * conf . Config
2020-11-08 12:12:49 +03:00
userSrvc services . IUserService
summarySrvc services . ISummaryService
2021-02-05 20:47:28 +03:00
heartbeatSrvc services . IHeartbeatService
2021-01-21 02:26:52 +03:00
aliasSrvc services . IAliasService
2020-11-08 12:12:49 +03:00
aggregationSrvc services . IAggregationService
languageMappingSrvc services . ILanguageMappingService
2021-06-12 11:44:19 +03:00
projectLabelSrvc services . IProjectLabelService
2021-02-05 20:47:28 +03:00
keyValueSrvc services . IKeyValueService
2021-04-10 11:48:06 +03:00
mailSrvc services . IMailService
2021-01-30 12:54:54 +03:00
httpClient * http . Client
2020-06-07 20:28:32 +03:00
}
var credentialsDecoder = schema . NewDecoder ( )
2021-02-05 20:47:28 +03:00
func NewSettingsHandler (
userService services . IUserService ,
heartbeatService services . IHeartbeatService ,
summaryService services . ISummaryService ,
aliasService services . IAliasService ,
aggregationService services . IAggregationService ,
languageMappingService services . ILanguageMappingService ,
2021-06-12 11:44:19 +03:00
projectLabelService services . IProjectLabelService ,
2021-02-05 20:47:28 +03:00
keyValueService services . IKeyValueService ,
2021-04-10 11:48:06 +03:00
mailService services . IMailService ,
2021-02-05 20:47:28 +03:00
) * SettingsHandler {
2020-06-07 20:28:32 +03:00
return & SettingsHandler {
2020-11-01 22:14:10 +03:00
config : conf . Get ( ) ,
2020-11-06 19:09:41 +03:00
summarySrvc : summaryService ,
2021-01-21 02:26:52 +03:00
aliasSrvc : aliasService ,
2020-11-06 19:09:41 +03:00
aggregationSrvc : aggregationService ,
2020-11-01 22:14:10 +03:00
languageMappingSrvc : languageMappingService ,
2021-06-12 11:44:19 +03:00
projectLabelSrvc : projectLabelService ,
2020-11-01 22:14:10 +03:00
userSrvc : userService ,
2021-02-05 20:47:28 +03:00
heartbeatSrvc : heartbeatService ,
keyValueSrvc : keyValueService ,
2021-04-10 11:48:06 +03:00
mailSrvc : mailService ,
2021-01-30 12:54:54 +03:00
httpClient : & http . Client { Timeout : 10 * time . Second } ,
2020-06-07 20:28:32 +03:00
}
}
2021-01-30 12:34:52 +03:00
func ( h * SettingsHandler ) RegisterRoutes ( router * mux . Router ) {
2021-02-03 23:28:02 +03:00
r := router . PathPrefix ( "/settings" ) . Subrouter ( )
r . Use (
2021-02-12 20:37:30 +03:00
middlewares . NewAuthenticateMiddleware ( h . userSrvc ) . WithRedirectTarget ( defaultErrorRedirectTarget ( ) ) . Handler ,
2021-02-03 23:28:02 +03:00
)
r . Methods ( http . MethodGet ) . HandlerFunc ( h . GetIndex )
r . Methods ( http . MethodPost ) . HandlerFunc ( h . PostIndex )
2021-01-30 12:34:52 +03:00
}
2020-06-07 20:28:32 +03:00
func ( h * SettingsHandler ) GetIndex ( w http . ResponseWriter , r * http . Request ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2020-11-06 23:19:54 +03:00
templates [ conf . SettingsTemplate ] . Execute ( w , h . buildViewModel ( r ) )
2020-06-07 20:28:32 +03:00
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) PostIndex ( w http . ResponseWriter , r * http . Request ) {
2020-06-07 20:28:32 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
if err := r . ParseForm ( ) ; err != nil {
2020-11-06 23:19:54 +03:00
w . WriteHeader ( http . StatusBadRequest )
2021-02-03 22:53:27 +03:00
templates [ conf . SettingsTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "missing form values" ) )
2020-06-07 20:28:32 +03:00
return
}
2021-02-03 22:53:27 +03:00
action := r . PostForm . Get ( "action" )
r . PostForm . Del ( "action" )
actionFunc := h . dispatchAction ( action )
if actionFunc == nil {
logbuch . Warn ( "failed to dispatch action '%s'" , action )
2020-11-06 23:19:54 +03:00
w . WriteHeader ( http . StatusBadRequest )
2021-02-03 22:53:27 +03:00
templates [ conf . SettingsTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( "unknown action requests" ) )
2020-06-07 20:28:32 +03:00
return
}
2021-02-03 22:53:27 +03:00
status , successMsg , errorMsg := actionFunc ( w , r )
// action responded itself
if status == - 1 {
2020-06-07 20:28:32 +03:00
return
}
2021-02-03 22:53:27 +03:00
if errorMsg != "" {
2021-02-05 20:47:28 +03:00
w . WriteHeader ( status )
2021-02-03 22:53:27 +03:00
templates [ conf . SettingsTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithError ( errorMsg ) )
2020-06-07 20:28:32 +03:00
return
}
2021-02-03 22:53:27 +03:00
if successMsg != "" {
2021-02-05 20:47:28 +03:00
w . WriteHeader ( status )
2021-02-03 22:53:27 +03:00
templates [ conf . SettingsTemplate ] . Execute ( w , h . buildViewModel ( r ) . WithSuccess ( successMsg ) )
return
}
templates [ conf . SettingsTemplate ] . Execute ( w , h . buildViewModel ( r ) )
}
func ( h * SettingsHandler ) dispatchAction ( action string ) action {
switch action {
case "change_password" :
return h . actionChangePassword
2021-02-21 15:02:11 +03:00
case "update_user" :
return h . actionUpdateUser
2021-02-03 22:53:27 +03:00
case "reset_apikey" :
return h . actionResetApiKey
case "delete_alias" :
return h . actionDeleteAlias
case "add_alias" :
return h . actionAddAlias
2021-06-12 11:44:19 +03:00
case "add_label" :
return h . actionAddLabel
case "delete_label" :
return h . actionDeleteLabel
2021-02-03 22:53:27 +03:00
case "delete_mapping" :
return h . actionDeleteLanguageMapping
case "add_mapping" :
return h . actionAddLanguageMapping
2021-02-07 00:32:03 +03:00
case "update_sharing" :
return h . actionUpdateSharing
2021-02-03 22:53:27 +03:00
case "toggle_wakatime" :
return h . actionSetWakatimeApiKey
2021-02-05 20:47:28 +03:00
case "import_wakatime" :
2021-08-06 17:36:01 +03:00
return h . actionImportWakatime
2021-02-03 22:53:27 +03:00
case "regenerate_summaries" :
return h . actionRegenerateSummaries
2022-03-17 13:55:13 +03:00
case "clear_data" :
return h . actionClearData
2021-02-03 22:53:27 +03:00
case "delete_account" :
return h . actionDeleteUser
}
return nil
}
2021-02-21 15:02:11 +03:00
func ( h * SettingsHandler ) actionUpdateUser ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-21 15:02:11 +03:00
var payload models . UserDataUpdate
if err := r . ParseForm ( ) ; err != nil {
return http . StatusBadRequest , "" , "missing parameters"
}
if err := credentialsDecoder . Decode ( & payload , r . PostForm ) ; err != nil {
return http . StatusBadRequest , "" , "missing parameters"
}
if ! payload . IsValid ( ) {
return http . StatusBadRequest , "" , "invalid parameters"
}
user . Email = payload . Email
2021-04-25 15:15:18 +03:00
user . Location = payload . Location
2021-04-30 17:20:08 +03:00
user . ReportsWeekly = payload . ReportsWeekly
2021-02-21 15:02:11 +03:00
if _ , err := h . userSrvc . Update ( user ) ; err != nil {
return http . StatusInternalServerError , "" , conf . ErrInternalServerError
}
2021-02-21 15:12:29 +03:00
return http . StatusOK , "user updated successfully" , ""
2021-02-21 15:02:11 +03:00
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionChangePassword ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-03 22:53:27 +03:00
var credentials models . CredentialsReset
if err := r . ParseForm ( ) ; err != nil {
return http . StatusBadRequest , "" , "missing parameters"
}
if err := credentialsDecoder . Decode ( & credentials , r . PostForm ) ; err != nil {
return http . StatusBadRequest , "" , "missing parameters"
}
if ! utils . CompareBcrypt ( user . Password , credentials . PasswordOld , h . config . Security . PasswordSalt ) {
return http . StatusUnauthorized , "" , "invalid credentials"
}
if ! credentials . IsValid ( ) {
return http . StatusBadRequest , "" , "invalid parameters"
}
2020-06-07 20:28:32 +03:00
user . Password = credentials . PasswordNew
2020-10-16 17:11:14 +03:00
if hash , err := utils . HashBcrypt ( user . Password , h . config . Security . PasswordSalt ) ; err != nil {
2021-02-12 21:25:59 +03:00
return http . StatusInternalServerError , "" , conf . ErrInternalServerError
2020-10-16 17:11:14 +03:00
} else {
user . Password = hash
2020-06-07 20:28:32 +03:00
}
if _ , err := h . userSrvc . Update ( user ) ; err != nil {
2021-02-12 21:25:59 +03:00
return http . StatusInternalServerError , "" , conf . ErrInternalServerError
2020-06-07 20:28:32 +03:00
}
login := & models . Login {
Username : user . ID ,
Password : user . Password ,
}
2021-01-31 16:34:54 +03:00
encoded , err := h . config . Security . SecureCookie . Encode ( models . AuthCookieKey , login . Username )
2020-06-07 20:28:32 +03:00
if err != nil {
2021-02-12 21:25:59 +03:00
return http . StatusInternalServerError , "" , conf . ErrInternalServerError
2020-06-07 20:28:32 +03:00
}
2022-01-17 10:25:29 +03:00
http . SetCookie ( w , h . config . CreateCookie ( models . AuthCookieKey , encoded ) )
2021-02-03 22:53:27 +03:00
return http . StatusOK , "password was updated successfully" , ""
2020-10-25 09:22:10 +03:00
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionResetApiKey ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
2020-10-25 09:22:10 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-03 22:53:27 +03:00
if _ , err := h . userSrvc . ResetApiKey ( user ) ; err != nil {
2021-02-12 21:25:59 +03:00
return http . StatusInternalServerError , "" , conf . ErrInternalServerError
2020-10-25 09:22:10 +03:00
}
2021-02-03 22:53:27 +03:00
msg := fmt . Sprintf ( "your new api key is: %s" , user . ApiKey )
return http . StatusOK , msg , ""
2020-06-07 20:28:32 +03:00
}
2021-02-07 00:32:03 +03:00
func ( h * SettingsHandler ) actionUpdateSharing ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
var err error
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-07 00:32:03 +03:00
defer h . userSrvc . FlushCache ( )
user . ShareProjects , err = strconv . ParseBool ( r . PostFormValue ( "share_projects" ) )
user . ShareLanguages , err = strconv . ParseBool ( r . PostFormValue ( "share_languages" ) )
user . ShareEditors , err = strconv . ParseBool ( r . PostFormValue ( "share_editors" ) )
user . ShareOSs , err = strconv . ParseBool ( r . PostFormValue ( "share_oss" ) )
user . ShareMachines , err = strconv . ParseBool ( r . PostFormValue ( "share_machines" ) )
2021-06-12 10:12:28 +03:00
user . ShareLabels , err = strconv . ParseBool ( r . PostFormValue ( "share_labels" ) )
2021-02-07 01:23:26 +03:00
user . ShareDataMaxDays , err = strconv . Atoi ( r . PostFormValue ( "max_days" ) )
2021-02-07 00:32:03 +03:00
if err != nil {
return http . StatusBadRequest , "" , "invalid input"
}
if _ , err := h . userSrvc . Update ( user ) ; err != nil {
return http . StatusInternalServerError , "" , "internal sever error"
}
return http . StatusOK , "settings updated" , ""
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionDeleteAlias ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
2021-01-21 02:26:52 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-01-21 02:26:52 +03:00
aliasKey := r . PostFormValue ( "key" )
aliasType , err := strconv . Atoi ( r . PostFormValue ( "type" ) )
if err != nil {
aliasType = 99 // nothing will be found later on
}
if aliases , err := h . aliasSrvc . GetByUserAndKeyAndType ( user . ID , aliasKey , uint8 ( aliasType ) ) ; err != nil {
2021-02-03 22:53:27 +03:00
return http . StatusNotFound , "" , "aliases not found"
2021-01-21 02:26:52 +03:00
} else if err := h . aliasSrvc . DeleteMulti ( aliases ) ; err != nil {
2021-02-03 22:53:27 +03:00
return http . StatusInternalServerError , "" , "could not delete aliases"
2021-01-21 02:26:52 +03:00
}
2021-02-03 22:53:27 +03:00
return http . StatusOK , "aliases deleted successfully" , ""
2021-01-21 02:26:52 +03:00
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionAddAlias ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
2021-01-21 02:26:52 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-01-21 02:26:52 +03:00
aliasKey := r . PostFormValue ( "key" )
aliasValue := r . PostFormValue ( "value" )
aliasType , err := strconv . Atoi ( r . PostFormValue ( "type" ) )
if err != nil {
aliasType = 99 // Alias.IsValid() will return false later on
}
alias := & models . Alias {
UserID : user . ID ,
Key : aliasKey ,
Value : aliasValue ,
Type : uint8 ( aliasType ) ,
}
if _ , err := h . aliasSrvc . Create ( alias ) ; err != nil {
// TODO: distinguish between bad request, conflict and server error
2021-02-03 22:53:27 +03:00
return http . StatusBadRequest , "" , "invalid input"
2021-01-21 02:26:52 +03:00
}
2021-02-03 22:53:27 +03:00
return http . StatusOK , "alias added successfully" , ""
2021-01-21 02:26:52 +03:00
}
2021-06-12 11:44:19 +03:00
func ( h * SettingsHandler ) actionAddLabel ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
user := middlewares . GetPrincipal ( r )
label := & models . ProjectLabel {
UserID : user . ID ,
ProjectKey : r . PostFormValue ( "key" ) ,
Label : r . PostFormValue ( "value" ) ,
}
if ! label . IsValid ( ) {
return http . StatusBadRequest , "" , "invalid input"
}
if _ , err := h . projectLabelSrvc . Create ( label ) ; err != nil {
// TODO: distinguish between bad request, conflict and server error
return http . StatusBadRequest , "" , "invalid input"
}
return http . StatusOK , "label added successfully" , ""
}
func ( h * SettingsHandler ) actionDeleteLabel ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
user := middlewares . GetPrincipal ( r )
2021-10-13 18:12:55 +03:00
labelKey := r . PostFormValue ( "key" ) // label key
labelValue := r . PostFormValue ( "value" ) // project key
2021-06-12 11:44:19 +03:00
2021-10-13 18:12:55 +03:00
labels , err := h . projectLabelSrvc . GetByUser ( user . ID )
2021-06-12 11:44:19 +03:00
if err != nil {
return http . StatusInternalServerError , "" , "could not delete label"
}
2021-10-13 18:12:55 +03:00
for _ , l := range labels {
if l . Label == labelKey && l . ProjectKey == labelValue {
if err := h . projectLabelSrvc . Delete ( l ) ; err != nil {
return http . StatusInternalServerError , "" , "could not delete label"
2021-06-12 11:44:19 +03:00
}
2021-10-13 18:12:55 +03:00
return http . StatusOK , "label deleted successfully" , ""
2021-06-12 11:44:19 +03:00
}
}
2021-10-13 18:12:55 +03:00
return http . StatusNotFound , "" , "label not found"
2021-06-12 11:44:19 +03:00
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionDeleteLanguageMapping ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
2020-06-07 20:28:32 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
2020-06-07 20:58:06 +03:00
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-03 22:53:27 +03:00
id , err := strconv . Atoi ( r . PostFormValue ( "mapping_id" ) )
if err != nil {
return http . StatusInternalServerError , "" , "could not delete mapping"
2020-06-07 20:58:06 +03:00
}
2021-03-22 23:19:08 +03:00
mapping , err := h . languageMappingSrvc . GetById ( uint ( id ) )
if err != nil || mapping == nil {
2021-02-03 22:53:27 +03:00
return http . StatusNotFound , "" , "mapping not found"
} else if mapping . UserID != user . ID {
return http . StatusForbidden , "" , "not allowed to delete mapping"
}
2021-03-22 23:19:08 +03:00
if err := h . languageMappingSrvc . Delete ( mapping ) ; err != nil {
2021-02-03 22:53:27 +03:00
return http . StatusInternalServerError , "" , "could not delete mapping"
}
return http . StatusOK , "mapping deleted successfully" , ""
2020-06-07 20:28:32 +03:00
}
2020-09-12 17:09:23 +03:00
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionAddLanguageMapping ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
2021-01-22 01:26:50 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-03 22:53:27 +03:00
extension := r . PostFormValue ( "extension" )
language := r . PostFormValue ( "language" )
2021-01-30 12:54:54 +03:00
2021-02-03 22:53:27 +03:00
if extension [ 0 ] == '.' {
extension = extension [ 1 : ]
2021-01-30 12:54:54 +03:00
}
2021-02-03 22:53:27 +03:00
mapping := & models . LanguageMapping {
UserID : user . ID ,
Extension : extension ,
Language : language ,
2021-01-22 01:26:50 +03:00
}
2021-02-03 22:53:27 +03:00
if _ , err := h . languageMappingSrvc . Create ( mapping ) ; err != nil {
return http . StatusConflict , "" , "mapping already exists"
}
return http . StatusOK , "mapping added successfully" , ""
2021-01-22 01:26:50 +03:00
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionSetWakatimeApiKey ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
2021-02-03 00:54:22 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-03 22:53:27 +03:00
apiKey := r . PostFormValue ( "api_key" )
2022-01-21 14:35:05 +03:00
apiUrl := r . PostFormValue ( "api_url" )
if apiUrl == conf . WakatimeApiUrl || apiKey == "" {
apiUrl = ""
}
2021-02-03 00:54:22 +03:00
2021-02-03 22:53:27 +03:00
// Healthcheck, if a new API key is set, i.e. the feature is activated
2022-01-21 14:35:05 +03:00
if ( user . WakatimeApiKey == "" && apiKey != "" ) && ! h . validateWakatimeKey ( apiKey , apiUrl ) {
2021-02-03 22:53:27 +03:00
return http . StatusBadRequest , "" , "failed to connect to WakaTime, API key invalid?"
}
2022-01-21 14:35:05 +03:00
if _ , err := h . userSrvc . SetWakatimeApiCredentials ( user , apiKey , apiUrl ) ; err != nil {
2021-02-12 21:25:59 +03:00
return http . StatusInternalServerError , "" , conf . ErrInternalServerError
2021-02-03 22:53:27 +03:00
}
return http . StatusOK , "Wakatime API Key updated successfully" , ""
2021-02-03 00:54:22 +03:00
}
2021-08-06 17:36:01 +03:00
func ( h * SettingsHandler ) actionImportWakatime ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
2020-11-06 19:09:41 +03:00
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-05 20:47:28 +03:00
if user . WakatimeApiKey == "" {
return http . StatusForbidden , "" , "not connected to wakatime"
}
2020-11-06 19:09:41 +03:00
2021-02-05 20:47:28 +03:00
kvKey := fmt . Sprintf ( "%s_%s" , conf . KeyLastImportImport , user . ID )
if ! h . config . IsDev ( ) {
lastImportKv := h . keyValueSrvc . MustGetString ( kvKey )
lastImport , _ := time . Parse ( time . RFC822 , lastImportKv . Value )
if time . Now ( ) . Sub ( lastImport ) < time . Duration ( h . config . App . ImportBackoffMin ) * time . Minute {
return http . StatusTooManyRequests ,
"" ,
fmt . Sprintf ( "Too many data imports. You are only allowed to request an import every %d minutes." , h . config . App . ImportBackoffMin )
}
2020-11-06 19:09:41 +03:00
}
2021-02-05 20:47:28 +03:00
go func ( user * models . User ) {
2021-04-10 11:48:06 +03:00
start := time . Now ( )
2021-02-05 20:47:28 +03:00
importer := imports . NewWakatimeHeartbeatImporter ( user . WakatimeApiKey )
countBefore , err := h . heartbeatSrvc . CountByUser ( user )
if err != nil {
println ( err )
}
2021-02-06 02:31:30 +03:00
var stream <- chan * models . Heartbeat
if latest , err := h . heartbeatSrvc . GetLatestByOriginAndUser ( imports . OriginWakatime , user ) ; latest == nil || err != nil {
stream = importer . ImportAll ( user )
} else {
// if an import has happened before, only import heartbeats newer than the latest of the last import
stream = importer . Import ( user , latest . Time . T ( ) , time . Now ( ) )
}
2021-02-05 20:47:28 +03:00
count := 0
batch := make ( [ ] * models . Heartbeat , 0 )
2021-04-04 10:43:49 +03:00
insert := func ( batch [ ] * models . Heartbeat ) {
if err := h . heartbeatSrvc . InsertBatch ( batch ) ; err != nil {
2022-02-17 14:20:22 +03:00
logbuch . Warn ( "failed to insert imported heartbeat, already existing? - %v" , err )
2021-04-04 10:43:49 +03:00
}
}
2021-02-06 02:31:30 +03:00
for hb := range stream {
2021-02-05 20:47:28 +03:00
count ++
batch = append ( batch , hb )
if len ( batch ) == h . config . App . ImportBatchSize {
2021-04-04 10:43:49 +03:00
insert ( batch )
2021-02-05 20:47:28 +03:00
batch = make ( [ ] * models . Heartbeat , 0 )
}
}
2021-04-04 10:43:49 +03:00
if len ( batch ) > 0 {
insert ( batch )
}
2021-02-05 20:47:28 +03:00
countAfter , _ := h . heartbeatSrvc . CountByUser ( user )
logbuch . Info ( "downloaded %d heartbeats for user '%s' (%d actually imported)" , count , user . ID , countAfter - countBefore )
h . regenerateSummaries ( user )
2021-04-10 11:48:06 +03:00
2021-06-11 00:22:04 +03:00
if ! user . HasData {
user . HasData = true
if _ , err := h . userSrvc . Update ( user ) ; err != nil {
2022-02-17 14:20:22 +03:00
conf . Log ( ) . Request ( r ) . Error ( "failed to set 'has_data' flag for user %s - %v" , user . ID , err )
2021-06-11 00:22:04 +03:00
}
}
2021-04-10 11:48:06 +03:00
if user . Email != "" {
if err := h . mailSrvc . SendImportNotification ( user , time . Now ( ) . Sub ( start ) , int ( countAfter - countBefore ) ) ; err != nil {
2022-02-17 14:20:22 +03:00
conf . Log ( ) . Request ( r ) . Error ( "failed to send import notification mail to %s - %v" , user . ID , err )
2021-04-10 11:48:06 +03:00
} else {
logbuch . Info ( "sent import notification mail to %s" , user . ID )
}
}
2021-02-05 20:47:28 +03:00
} ( user )
h . keyValueSrvc . PutString ( & models . KeyStringValue {
Key : kvKey ,
Value : time . Now ( ) . Format ( time . RFC822 ) ,
} )
2021-02-03 22:53:27 +03:00
2021-04-10 11:18:09 +03:00
return http . StatusAccepted , "Import started. This will take several minutes. Please check back later." , ""
2021-02-05 20:47:28 +03:00
}
func ( h * SettingsHandler ) actionRegenerateSummaries ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-25 01:31:04 +03:00
go func ( user * models . User ) {
if err := h . regenerateSummaries ( user ) ; err != nil {
2022-02-17 14:20:22 +03:00
conf . Log ( ) . Request ( r ) . Error ( "failed to regenerate summaries for user '%s' - %v" , user . ID , err )
2021-03-25 01:31:04 +03:00
}
2021-03-26 15:10:10 +03:00
} ( middlewares . GetPrincipal ( r ) )
2020-11-06 19:09:41 +03:00
2022-02-17 14:20:22 +03:00
return http . StatusAccepted , "summaries are being regenerated - this may take a up to a couple of minutes, please come back later" , ""
2021-02-03 22:53:27 +03:00
}
2022-03-17 13:55:13 +03:00
func ( h * SettingsHandler ) actionClearData ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
user := middlewares . GetPrincipal ( r )
logbuch . Info ( "user '%s' requested to delete all data" , user . ID )
go func ( user * models . User ) {
logbuch . Info ( "deleting summaries for user '%s'" , user . ID )
if err := h . summarySrvc . DeleteByUser ( user . ID ) ; err != nil {
logbuch . Error ( "failed to clear summaries: %v" , err )
}
logbuch . Info ( "deleting heartbeats for user '%s'" , user . ID )
if err := h . heartbeatSrvc . DeleteByUser ( user ) ; err != nil {
logbuch . Error ( "failed to clear heartbeats: %v" , err )
}
} ( user )
return http . StatusAccepted , "deletion in progress, this may take a couple of seconds" , ""
}
2021-02-03 22:53:27 +03:00
func ( h * SettingsHandler ) actionDeleteUser ( w http . ResponseWriter , r * http . Request ) ( int , string , string ) {
if h . config . IsDev ( ) {
loadTemplates ( )
}
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-02-03 22:53:27 +03:00
go func ( user * models . User ) {
logbuch . Info ( "deleting user '%s' shortly" , user . ID )
time . Sleep ( 5 * time . Minute )
if err := h . userSrvc . Delete ( user ) ; err != nil {
2022-02-17 14:20:22 +03:00
conf . Log ( ) . Request ( r ) . Error ( "failed to delete user '%s' - %v" , user . ID , err )
2021-02-03 22:53:27 +03:00
} else {
logbuch . Info ( "successfully deleted user '%s'" , user . ID )
}
} ( user )
2022-01-17 10:25:29 +03:00
http . SetCookie ( w , h . config . GetClearCookie ( models . AuthCookieKey ) )
2021-02-03 22:53:27 +03:00
http . Redirect ( w , r , fmt . Sprintf ( "%s/?success=%s" , h . config . Server . BasePath , "Your account will be deleted in a few minutes. Sorry to you go." ) , http . StatusFound )
return - 1 , "" , ""
2020-11-06 23:19:54 +03:00
}
2022-01-21 14:35:05 +03:00
func ( h * SettingsHandler ) validateWakatimeKey ( apiKey string , baseUrl string ) bool {
if baseUrl == "" {
baseUrl = conf . WakatimeApiUrl
}
2021-01-30 12:54:54 +03:00
headers := http . Header {
"Accept" : [ ] string { "application/json" } ,
"Authorization" : [ ] string {
fmt . Sprintf ( "Basic %s" , base64 . StdEncoding . EncodeToString ( [ ] byte ( apiKey ) ) ) ,
} ,
}
request , err := http . NewRequest (
http . MethodGet ,
2022-01-21 14:35:05 +03:00
baseUrl + conf . WakatimeApiUserUrl ,
2021-01-30 12:54:54 +03:00
nil ,
)
if err != nil {
return false
}
request . Header = headers
response , err := h . httpClient . Do ( request )
if err != nil || response . StatusCode < 200 || response . StatusCode >= 300 {
return false
}
return true
}
2021-02-05 20:47:28 +03:00
func ( h * SettingsHandler ) regenerateSummaries ( user * models . User ) error {
logbuch . Info ( "clearing summaries for user '%s'" , user . ID )
if err := h . summarySrvc . DeleteByUser ( user . ID ) ; err != nil {
logbuch . Error ( "failed to clear summaries: %v" , err )
return err
}
if err := h . aggregationSrvc . Run ( map [ string ] bool { user . ID : true } ) ; err != nil {
logbuch . Error ( "failed to regenerate summaries: %v" , err )
return err
}
return nil
}
2020-11-06 23:19:54 +03:00
func ( h * SettingsHandler ) buildViewModel ( r * http . Request ) * view . SettingsViewModel {
2021-03-26 15:10:10 +03:00
user := middlewares . GetPrincipal ( r )
2021-06-12 11:44:19 +03:00
// mappings
2020-11-06 23:19:54 +03:00
mappings , _ := h . languageMappingSrvc . GetByUser ( user . ID )
2021-06-12 11:44:19 +03:00
// aliases
aliases , err := h . aliasSrvc . GetByUser ( user . ID )
if err != nil {
conf . Log ( ) . Request ( r ) . Error ( "error while building alias map - %v" , err )
return & view . SettingsViewModel { Error : criticalError }
}
2021-01-21 02:26:52 +03:00
aliasMap := make ( map [ string ] [ ] * models . Alias )
for _ , a := range aliases {
k := fmt . Sprintf ( "%s_%d" , a . Key , a . Type )
if _ , ok := aliasMap [ k ] ; ! ok {
aliasMap [ k ] = [ ] * models . Alias { a }
} else {
aliasMap [ k ] = append ( aliasMap [ k ] , a )
}
}
combinedAliases := make ( [ ] * view . SettingsVMCombinedAlias , 0 )
for _ , l := range aliasMap {
ca := & view . SettingsVMCombinedAlias {
Key : l [ 0 ] . Key ,
Type : l [ 0 ] . Type ,
Values : make ( [ ] string , len ( l ) ) ,
}
for i , a := range l {
ca . Values [ i ] = a . Value
}
combinedAliases = append ( combinedAliases , ca )
}
2021-06-12 11:44:19 +03:00
// labels
2021-10-13 18:12:55 +03:00
labelMap , err := h . projectLabelSrvc . GetByUserGroupedInverted ( user . ID )
2021-06-12 11:44:19 +03:00
if err != nil {
conf . Log ( ) . Request ( r ) . Error ( "error while building settings project label map - %v" , err )
return & view . SettingsViewModel { Error : criticalError }
}
combinedLabels := make ( [ ] * view . SettingsVMCombinedLabel , 0 )
for _ , l := range labelMap {
cl := & view . SettingsVMCombinedLabel {
2021-10-13 18:12:55 +03:00
Key : l [ 0 ] . Label ,
2021-06-12 11:44:19 +03:00
Values : make ( [ ] string , len ( l ) ) ,
}
for i , l1 := range l {
2021-10-13 18:12:55 +03:00
cl . Values [ i ] = l1 . ProjectKey
2021-06-12 11:44:19 +03:00
}
combinedLabels = append ( combinedLabels , cl )
}
sort . Slice ( combinedLabels , func ( i , j int ) bool {
return strings . Compare ( combinedLabels [ i ] . Key , combinedLabels [ j ] . Key ) < 0
} )
// projects
2022-01-13 19:10:24 +03:00
projects , err := routeutils . GetEffectiveProjectsList ( user , h . heartbeatSrvc , h . aliasSrvc )
2021-06-12 11:44:19 +03:00
if err != nil {
conf . Log ( ) . Request ( r ) . Error ( "error while fetching projects - %v" , err )
return & view . SettingsViewModel { Error : criticalError }
}
2020-11-06 23:19:54 +03:00
return & view . SettingsViewModel {
User : user ,
LanguageMappings : mappings ,
2021-01-21 02:26:52 +03:00
Aliases : combinedAliases ,
2021-06-12 11:44:19 +03:00
Labels : combinedLabels ,
Projects : projects ,
2021-12-16 14:39:41 +03:00
ApiKey : user . ApiKey ,
2020-11-06 23:19:54 +03:00
Success : r . URL . Query ( ) . Get ( "success" ) ,
Error : r . URL . Query ( ) . Get ( "error" ) ,
}
2020-11-06 19:09:41 +03:00
}