1
0
mirror of https://github.com/muety/wakapi.git synced 2023-08-10 21:12:56 +03:00

refactor: settings routes and actions

This commit is contained in:
Ferdinand Mütsch
2021-02-03 20:53:27 +01:00
parent a60c725d38
commit 4838300086
3 changed files with 184 additions and 153 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/muety/wakapi/utils" "github.com/muety/wakapi/utils"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net/http"
"path" "path"
"strings" "strings"
) )
@ -16,6 +17,8 @@ func Init() {
loadTemplates() loadTemplates()
} }
type action func(w http.ResponseWriter, r *http.Request) (int, string, string)
var templates map[string]*template.Template var templates map[string]*template.Template
func loadTemplates() { func loadTemplates() {

View File

@ -42,16 +42,7 @@ func NewSettingsHandler(userService services.IUserService, summaryService servic
func (h *SettingsHandler) RegisterRoutes(router *mux.Router) { func (h *SettingsHandler) RegisterRoutes(router *mux.Router) {
router.Methods(http.MethodGet).HandlerFunc(h.GetIndex) router.Methods(http.MethodGet).HandlerFunc(h.GetIndex)
router.Path("/credentials").Methods(http.MethodPost).HandlerFunc(h.PostCredentials) router.Methods(http.MethodPost).HandlerFunc(h.PostIndex)
router.Path("/aliases").Methods(http.MethodPost).HandlerFunc(h.PostAlias)
router.Path("/aliases/delete").Methods(http.MethodPost).HandlerFunc(h.DeleteAlias)
router.Path("/language_mappings").Methods(http.MethodPost).HandlerFunc(h.PostLanguageMapping)
router.Path("/language_mappings/delete").Methods(http.MethodPost).HandlerFunc(h.DeleteLanguageMapping)
router.Path("/reset").Methods(http.MethodPost).HandlerFunc(h.PostResetApiKey)
router.Path("/badges").Methods(http.MethodPost).HandlerFunc(h.PostToggleBadges)
router.Path("/user/delete").Methods(http.MethodPost).HandlerFunc(h.DeleteUser)
router.Path("/wakatime_integration").Methods(http.MethodPost).HandlerFunc(h.PostSetWakatimeApiKey)
router.Path("/regenerate").Methods(http.MethodPost).HandlerFunc(h.PostRegenerateSummaries)
} }
func (h *SettingsHandler) RegisterAPIRoutes(router *mux.Router) {} func (h *SettingsHandler) RegisterAPIRoutes(router *mux.Router) {}
@ -64,7 +55,73 @@ func (h *SettingsHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r)) templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r))
} }
func (h *SettingsHandler) PostCredentials(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) PostIndex(w http.ResponseWriter, r *http.Request) {
if h.config.IsDev() {
loadTemplates()
}
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("missing form values"))
return
}
action := r.PostForm.Get("action")
r.PostForm.Del("action")
actionFunc := h.dispatchAction(action)
if actionFunc == nil {
logbuch.Warn("failed to dispatch action '%s'", action)
w.WriteHeader(http.StatusBadRequest)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("unknown action requests"))
return
}
status, successMsg, errorMsg := actionFunc(w, r)
// action responded itself
if status == -1 {
return
}
if errorMsg != "" {
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError(errorMsg))
return
}
if successMsg != "" {
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
case "reset_apikey":
return h.actionResetApiKey
case "delete_alias":
return h.actionDeleteAlias
case "add_alias":
return h.actionAddAlias
case "delete_mapping":
return h.actionDeleteLanguageMapping
case "add_mapping":
return h.actionAddLanguageMapping
case "toggle_badges":
return h.actionToggleBadges
case "toggle_wakatime":
return h.actionSetWakatimeApiKey
case "regenerate_summaries":
return h.actionRegenerateSummaries
case "delete_account":
return h.actionDeleteUser
}
return nil
}
func (h *SettingsHandler) actionChangePassword(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
@ -73,41 +130,29 @@ func (h *SettingsHandler) PostCredentials(w http.ResponseWriter, r *http.Request
var credentials models.CredentialsReset var credentials models.CredentialsReset
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest) return http.StatusBadRequest, "", "missing parameters"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("missing parameters"))
return
} }
if err := credentialsDecoder.Decode(&credentials, r.PostForm); err != nil { if err := credentialsDecoder.Decode(&credentials, r.PostForm); err != nil {
w.WriteHeader(http.StatusBadRequest) return http.StatusBadRequest, "", "missing parameters"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("missing parameters"))
return
} }
if !utils.CompareBcrypt(user.Password, credentials.PasswordOld, h.config.Security.PasswordSalt) { if !utils.CompareBcrypt(user.Password, credentials.PasswordOld, h.config.Security.PasswordSalt) {
w.WriteHeader(http.StatusUnauthorized) return http.StatusUnauthorized, "", "invalid credentials"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("invalid credentials"))
return
} }
if !credentials.IsValid() { if !credentials.IsValid() {
w.WriteHeader(http.StatusBadRequest) return http.StatusBadRequest, "", "invalid parameters"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("invalid parameters"))
return
} }
user.Password = credentials.PasswordNew user.Password = credentials.PasswordNew
if hash, err := utils.HashBcrypt(user.Password, h.config.Security.PasswordSalt); err != nil { if hash, err := utils.HashBcrypt(user.Password, h.config.Security.PasswordSalt); err != nil {
w.WriteHeader(http.StatusInternalServerError) return http.StatusInternalServerError, "", "internal server error"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("internal server error"))
return
} else { } else {
user.Password = hash user.Password = hash
} }
if _, err := h.userSrvc.Update(user); err != nil { if _, err := h.userSrvc.Update(user); err != nil {
w.WriteHeader(http.StatusInternalServerError) return http.StatusInternalServerError, "", "internal server error"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("internal server error"))
return
} }
login := &models.Login{ login := &models.Login{
@ -116,75 +161,28 @@ func (h *SettingsHandler) PostCredentials(w http.ResponseWriter, r *http.Request
} }
encoded, err := h.config.Security.SecureCookie.Encode(models.AuthCookieKey, login.Username) encoded, err := h.config.Security.SecureCookie.Encode(models.AuthCookieKey, login.Username)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) return http.StatusInternalServerError, "", "internal server error"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("internal server error"))
return
} }
http.SetCookie(w, h.config.CreateCookie(models.AuthCookieKey, encoded, "/")) http.SetCookie(w, h.config.CreateCookie(models.AuthCookieKey, encoded, "/"))
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("password was updated successfully")) return http.StatusOK, "password was updated successfully", ""
} }
func (h *SettingsHandler) DeleteLanguageMapping(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) actionResetApiKey(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
user := r.Context().Value(models.UserKey).(*models.User) user := r.Context().Value(models.UserKey).(*models.User)
id, err := strconv.Atoi(r.PostFormValue("mapping_id")) if _, err := h.userSrvc.ResetApiKey(user); err != nil {
if err != nil { return http.StatusInternalServerError, "", "internal server error"
w.WriteHeader(http.StatusInternalServerError)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("could not delete mapping"))
return
} }
if mapping, err := h.languageMappingSrvc.GetById(uint(id)); err != nil || mapping == nil { msg := fmt.Sprintf("your new api key is: %s", user.ApiKey)
w.WriteHeader(http.StatusNotFound) return http.StatusOK, msg, ""
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("mapping not found"))
return
} else if mapping.UserID != user.ID {
w.WriteHeader(http.StatusForbidden)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("not allowed to delete mapping"))
return
}
if err := h.languageMappingSrvc.Delete(&models.LanguageMapping{ID: uint(id)}); err != nil {
w.WriteHeader(http.StatusInternalServerError)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("could not delete mapping"))
return
}
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("mapping deleted successfully"))
} }
func (h *SettingsHandler) PostLanguageMapping(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) actionDeleteAlias(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
extension := r.PostFormValue("extension")
language := r.PostFormValue("language")
if extension[0] == '.' {
extension = extension[1:]
}
mapping := &models.LanguageMapping{
UserID: user.ID,
Extension: extension,
Language: language,
}
if _, err := h.languageMappingSrvc.Create(mapping); err != nil {
w.WriteHeader(http.StatusConflict)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("mapping already exists"))
return
}
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("mapping added successfully"))
}
func (h *SettingsHandler) DeleteAlias(w http.ResponseWriter, r *http.Request) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
@ -197,19 +195,15 @@ func (h *SettingsHandler) DeleteAlias(w http.ResponseWriter, r *http.Request) {
} }
if aliases, err := h.aliasSrvc.GetByUserAndKeyAndType(user.ID, aliasKey, uint8(aliasType)); err != nil { if aliases, err := h.aliasSrvc.GetByUserAndKeyAndType(user.ID, aliasKey, uint8(aliasType)); err != nil {
w.WriteHeader(http.StatusNotFound) return http.StatusNotFound, "", "aliases not found"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("aliases not found"))
return
} else if err := h.aliasSrvc.DeleteMulti(aliases); err != nil { } else if err := h.aliasSrvc.DeleteMulti(aliases); err != nil {
w.WriteHeader(http.StatusInternalServerError) return http.StatusInternalServerError, "", "could not delete aliases"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("could not delete aliases"))
return
} }
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("aliases deleted successfully")) return http.StatusOK, "aliases deleted successfully", ""
} }
func (h *SettingsHandler) PostAlias(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) actionAddAlias(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
@ -229,32 +223,76 @@ func (h *SettingsHandler) PostAlias(w http.ResponseWriter, r *http.Request) {
} }
if _, err := h.aliasSrvc.Create(alias); err != nil { if _, err := h.aliasSrvc.Create(alias); err != nil {
w.WriteHeader(http.StatusBadRequest)
// TODO: distinguish between bad request, conflict and server error // TODO: distinguish between bad request, conflict and server error
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("invalid input")) return http.StatusBadRequest, "", "invalid input"
return
} }
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("alias added successfully")) return http.StatusOK, "alias added successfully", ""
} }
func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) actionDeleteLanguageMapping(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
user := r.Context().Value(models.UserKey).(*models.User) user := r.Context().Value(models.UserKey).(*models.User)
if _, err := h.userSrvc.ResetApiKey(user); err != nil { id, err := strconv.Atoi(r.PostFormValue("mapping_id"))
w.WriteHeader(http.StatusInternalServerError) if err != nil {
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("internal server error")) return http.StatusInternalServerError, "", "could not delete mapping"
return
} }
msg := fmt.Sprintf("your new api key is: %s", user.ApiKey) if mapping, err := h.languageMappingSrvc.GetById(uint(id)); err != nil || mapping == nil {
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess(msg)) return http.StatusNotFound, "", "mapping not found"
} else if mapping.UserID != user.ID {
return http.StatusForbidden, "", "not allowed to delete mapping"
}
if err := h.languageMappingSrvc.Delete(&models.LanguageMapping{ID: uint(id)}); err != nil {
return http.StatusInternalServerError, "", "could not delete mapping"
}
return http.StatusOK, "mapping deleted successfully", ""
} }
func (h *SettingsHandler) PostSetWakatimeApiKey(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) actionAddLanguageMapping(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
extension := r.PostFormValue("extension")
language := r.PostFormValue("language")
if extension[0] == '.' {
extension = extension[1:]
}
mapping := &models.LanguageMapping{
UserID: user.ID,
Extension: extension,
Language: language,
}
if _, err := h.languageMappingSrvc.Create(mapping); err != nil {
return http.StatusConflict, "", "mapping already exists"
}
return http.StatusOK, "mapping added successfully", ""
}
func (h *SettingsHandler) actionToggleBadges(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
if _, err := h.userSrvc.ToggleBadges(user); err != nil {
return http.StatusInternalServerError, "", "internal server error"
}
return http.StatusOK, "", ""
}
func (h *SettingsHandler) actionSetWakatimeApiKey(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
@ -264,35 +302,39 @@ func (h *SettingsHandler) PostSetWakatimeApiKey(w http.ResponseWriter, r *http.R
// Healthcheck, if a new API key is set, i.e. the feature is activated // Healthcheck, if a new API key is set, i.e. the feature is activated
if (user.WakatimeApiKey == "" && apiKey != "") && !h.validateWakatimeKey(apiKey) { if (user.WakatimeApiKey == "" && apiKey != "") && !h.validateWakatimeKey(apiKey) {
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("failed to connect to WakaTime, API key invalid?")) return http.StatusBadRequest, "", "failed to connect to WakaTime, API key invalid?"
return
} }
if _, err := h.userSrvc.SetWakatimeApiKey(user, apiKey); err != nil { if _, err := h.userSrvc.SetWakatimeApiKey(user, apiKey); err != nil {
w.WriteHeader(http.StatusInternalServerError) return http.StatusInternalServerError, "", "internal server error"
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("internal server error"))
return
} }
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("Wakatime API Key updated successfully")) return http.StatusOK, "Wakatime API Key updated successfully", ""
} }
func (h *SettingsHandler) PostToggleBadges(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
user := r.Context().Value(models.UserKey).(*models.User) user := r.Context().Value(models.UserKey).(*models.User)
if _, err := h.userSrvc.ToggleBadges(user); err != nil {
w.WriteHeader(http.StatusInternalServerError) logbuch.Info("clearing summaries for user '%s'", user.ID)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("internal server error")) if err := h.summarySrvc.DeleteByUser(user.ID); err != nil {
return logbuch.Error("failed to clear summaries: %v", err)
return http.StatusInternalServerError, "", "failed to delete old summaries"
} }
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r)) if err := h.aggregationSrvc.Run(map[string]bool{user.ID: true}); err != nil {
logbuch.Error("failed to regenerate summaries: %v", err)
return http.StatusInternalServerError, "", "failed to generate aggregations"
}
return http.StatusOK, "summaries are being regenerated this may take a few seconds", ""
} }
func (h *SettingsHandler) DeleteUser(w http.ResponseWriter, r *http.Request) { func (h *SettingsHandler) actionDeleteUser(w http.ResponseWriter, r *http.Request) (int, string, string) {
if h.config.IsDev() { if h.config.IsDev() {
loadTemplates() loadTemplates()
} }
@ -310,31 +352,7 @@ func (h *SettingsHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, h.config.GetClearCookie(models.AuthCookieKey, "/")) http.SetCookie(w, h.config.GetClearCookie(models.AuthCookieKey, "/"))
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) 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, "", ""
func (h *SettingsHandler) PostRegenerateSummaries(w http.ResponseWriter, r *http.Request) {
if h.config.IsDev() {
loadTemplates()
}
user := r.Context().Value(models.UserKey).(*models.User)
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)
w.WriteHeader(http.StatusInternalServerError)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("failed to delete old summaries"))
return
}
if err := h.aggregationSrvc.Run(map[string]bool{user.ID: true}); err != nil {
logbuch.Error("failed to regenerate summaries: %v", err)
w.WriteHeader(http.StatusInternalServerError)
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithError("failed to generate aggregations"))
return
}
templates[conf.SettingsTemplate].Execute(w, h.buildViewModel(r).WithSuccess("summaries are being regenerated this may take a few seconds"))
} }
func (h *SettingsHandler) validateWakatimeKey(apiKey string) bool { func (h *SettingsHandler) validateWakatimeKey(apiKey string) bool {

View File

@ -64,7 +64,8 @@
Change Password Change Password
</h2> </h2>
<form class="mt-10" action="settings/credentials" method="post"> <form class="mt-10" action="" method="post">
<input type="hidden" name="action" value="change_password">
<div class="mb-8"> <div class="mb-8">
<label class="inline-block text-sm mb-1 text-gray-500" for="password_old">Current Password</label> <label class="inline-block text-sm mb-1 text-gray-500" for="password_old">Current Password</label>
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3" <input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
@ -96,7 +97,8 @@
Reset API Key Reset API Key
</h2> </h2>
<form class="mt-6" action="settings/reset" method="post"> <form class="mt-6" action="" method="post">
<input type="hidden" name="action" value="reset_apikey">
<div class="text-gray-300 text-sm mb-4"> <div class="text-gray-300 text-sm mb-4">
<strong>⚠️ Caution:</strong> Resetting your API key requires you to update your <span <strong>⚠️ Caution:</strong> Resetting your API key requires you to update your <span
class="font-mono">.wakatime.cfg</span> files on all of your computers to make the WakaTime class="font-mono">.wakatime.cfg</span> files on all of your computers to make the WakaTime
@ -141,7 +143,8 @@
are mapped to <span class="underline">{{ $alias.Type | typeName }}</span> <span are mapped to <span class="underline">{{ $alias.Type | typeName }}</span> <span
class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono">{{ $alias.Key }}</span>. class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono">{{ $alias.Key }}</span>.
</div> </div>
<form class="float-right" action="settings/aliases/delete" method="post"> <form class="float-right" action="" method="post">
<input type="hidden" name="action" value="delete_alias">
<input type="hidden" id="delete_alias_key" name="key" required value="{{ $alias.Key }}"> <input type="hidden" id="delete_alias_key" name="key" required value="{{ $alias.Key }}">
<input type="hidden" id="delete_alias_type" name="type" required value="{{ $alias.Type }}"> <input type="hidden" id="delete_alias_type" name="type" required value="{{ $alias.Type }}">
<button type="submit" <button type="submit"
@ -155,7 +158,8 @@
{{end}} {{end}}
<h3 class="inline-block font-semibold text-md border-b border-green-700 text-white mb-2">Add Rule</h3> <h3 class="inline-block font-semibold text-md border-b border-green-700 text-white mb-2">Add Rule</h3>
<form action="settings/aliases" method="post"> <form action="" method="post">
<input type="hidden" name="action" value="add_alias">
<div class="flex items-center mt-2 w-full text-gray-500 text-sm"> <div class="flex items-center mt-2 w-full text-gray-500 text-sm">
<span class="mr-2">Map</span> <span class="mr-2">Map</span>
<select name="type" id="select-type" <select name="type" id="select-type"
@ -203,7 +207,8 @@
then change the <span class="underline">language</span> to <span then change the <span class="underline">language</span> to <span
class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono mr-1">{{ $mapping.Language }}</span> class="text-white text-xs bg-gray-900 rounded py-1 px-2 font-mono mr-1">{{ $mapping.Language }}</span>
</div> </div>
<form class="float-right" action="settings/language_mappings/delete" method="post"> <form class="float-right" action="" method="post">
<input type="hidden" name="action" value="delete_mapping">
<input type="hidden" id="mapping_id" name="mapping_id" required value="{{ $mapping.ID }}"> <input type="hidden" id="mapping_id" name="mapping_id" required value="{{ $mapping.ID }}">
<button type="submit" <button type="submit"
class="py-1 px-3 rounded border border-red-500 hover:border-red-600 text-gray-400 text-sm"> class="py-1 px-3 rounded border border-red-500 hover:border-red-600 text-gray-400 text-sm">
@ -216,7 +221,8 @@
{{end}} {{end}}
<h3 class="inline-block font-semibold text-md border-b border-green-700 text-white">Add Rule</h3> <h3 class="inline-block font-semibold text-md border-b border-green-700 text-white">Add Rule</h3>
<form action="settings/language_mappings" method="post"> <form action="" method="post">
<input type="hidden" name="action" value="add_mapping">
<div class="flex items-center w-full text-gray-500 text-sm"> <div class="flex items-center w-full text-gray-500 text-sm">
<span class="mr-2">When filename ends in</span> <span class="mr-2">When filename ends in</span>
<input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded py-1 px-3" <input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded py-1 px-3"
@ -241,7 +247,8 @@
Badges Badges
</div> </div>
<form class="mt-6" action="settings/badges" method="post"> <form class="mt-6" action="" method="post">
<input type="hidden" name="action" value="toggle_badges">
<div class="text-gray-300 text-sm mb-4"> <div class="text-gray-300 text-sm mb-4">
{{ if .User.BadgesEnabled }} {{ if .User.BadgesEnabled }}
<p>Badges are currently enabled. You can disable the feature by deactivating the respective API <p>Badges are currently enabled. You can disable the feature by deactivating the respective API
@ -328,7 +335,8 @@
target="_blank">get your API key</a> and paste it here.</p> target="_blank">get your API key</a> and paste it here.</p>
</div> </div>
<form action="settings/wakatime_integration" method="post"> <form action="" method="post">
<input type="hidden" name="action" value="toggle_wakatime">
{{ $placeholderText := "Paste your WakaTime API key here ..." }} {{ $placeholderText := "Paste your WakaTime API key here ..." }}
{{ if .User.WakatimeApiKey }} {{ if .User.WakatimeApiKey }}
@ -390,7 +398,8 @@
</p> </p>
</div> </div>
<div class="mt-6 flex justify-center"> <div class="mt-6 flex justify-center">
<form action="settings/regenerate" method="post" id="form-regenerate-summaries"> <form action="" method="post" id="form-regenerate-summaries">
<input type="hidden" name="action" value="regenerate_summaries">
<button type="button" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm" <button type="button" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm"
id="btn-regenerate-summaries"> id="btn-regenerate-summaries">
Clear & Regenerate Clear & Regenerate
@ -408,8 +417,9 @@
</p> </p>
</div> </div>
<div class="mt-6 flex justify-center"> <div class="mt-6 flex justify-center">
<form action="settings/user/delete" method="post" id="form-delete-user"> <form action="" method="post" id="form-delete-user">
<button type="button" onclick="console.log('boom')" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm" <input type="hidden" name="action" value="delete_account">
<button type="button" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm"
id="btn-confirm-delete-user"> id="btn-confirm-delete-user">
Delete my Account Delete my Account
</button> </button>