mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
chore: minor code enhancements
This commit is contained in:
parent
4d22756b8a
commit
21b822de42
9
config/templates.go
Normal file
9
config/templates.go
Normal file
@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
IndexTemplate = "index.tpl.html"
|
||||
ImprintTemplate = "imprint.tpl.html"
|
||||
SignupTemplate = "signup.tpl.html"
|
||||
SettingsTemplate = "settings.tpl.html"
|
||||
SummaryTemplate = "summary.tpl.html"
|
||||
)
|
1
main.go
1
main.go
@ -64,7 +64,6 @@ func main() {
|
||||
log.Println(err)
|
||||
log.Fatal("could not connect to database")
|
||||
}
|
||||
// TODO: Graceful shutdown
|
||||
defer db.Close()
|
||||
|
||||
// Migrate database schema
|
||||
|
@ -1,4 +1,5 @@
|
||||
package models
|
||||
|
||||
func init() {
|
||||
// nothing no init here, yet
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package routes
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/schema"
|
||||
config2 "github.com/muety/wakapi/config"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/services"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
type IndexHandler struct {
|
||||
config *config2.Config
|
||||
config *conf.Config
|
||||
userSrvc *services.UserService
|
||||
keyValueSrvc *services.KeyValueService
|
||||
}
|
||||
@ -24,7 +24,7 @@ var signupDecoder = schema.NewDecoder()
|
||||
|
||||
func NewIndexHandler(userService *services.UserService, keyValueService *services.KeyValueService) *IndexHandler {
|
||||
return &IndexHandler{
|
||||
config: config2.Get(),
|
||||
config: conf.Get(),
|
||||
userSrvc: userService,
|
||||
keyValueSrvc: keyValueService,
|
||||
}
|
||||
@ -44,7 +44,7 @@ func (h *IndexHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
templates["index.tpl.html"].Execute(w, nil)
|
||||
templates[conf.IndexTemplate].Execute(w, nil)
|
||||
}
|
||||
|
||||
func (h *IndexHandler) GetImprint(w http.ResponseWriter, r *http.Request) {
|
||||
@ -57,7 +57,7 @@ func (h *IndexHandler) GetImprint(w http.ResponseWriter, r *http.Request) {
|
||||
text = data.Value
|
||||
}
|
||||
|
||||
templates["imprint.tpl.html"].Execute(w, &struct {
|
||||
templates[conf.ImprintTemplate].Execute(w, &struct {
|
||||
HtmlText string
|
||||
}{HtmlText: text})
|
||||
}
|
||||
@ -133,11 +133,11 @@ func (h *IndexHandler) GetSignup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if handleAlerts(w, r, "signup.tpl.html") {
|
||||
if handleAlerts(w, r, conf.SignupTemplate) {
|
||||
return
|
||||
}
|
||||
|
||||
templates["signup.tpl.html"].Execute(w, nil)
|
||||
templates[conf.SignupTemplate].Execute(w, nil)
|
||||
}
|
||||
|
||||
func (h *IndexHandler) PostSignup(w http.ResponseWriter, r *http.Request) {
|
||||
@ -152,26 +152,26 @@ func (h *IndexHandler) PostSignup(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var signup models.Signup
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondAlert(w, "missing parameters", "", "signup.tpl.html", http.StatusBadRequest)
|
||||
respondAlert(w, "missing parameters", "", conf.SignupTemplate, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := signupDecoder.Decode(&signup, r.PostForm); err != nil {
|
||||
respondAlert(w, "missing parameters", "", "signup.tpl.html", http.StatusBadRequest)
|
||||
respondAlert(w, "missing parameters", "", conf.SignupTemplate, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !signup.IsValid() {
|
||||
respondAlert(w, "invalid parameters", "", "signup.tpl.html", http.StatusBadRequest)
|
||||
respondAlert(w, "invalid parameters", "", conf.SignupTemplate, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, created, err := h.userSrvc.CreateOrGet(&signup)
|
||||
if err != nil {
|
||||
respondAlert(w, "failed to create new user", "", "signup.tpl.html", http.StatusInternalServerError)
|
||||
respondAlert(w, "failed to create new user", "", conf.SignupTemplate, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !created {
|
||||
respondAlert(w, "user already existing", "", "signup.tpl.html", http.StatusConflict)
|
||||
respondAlert(w, "user already existing", "", conf.SignupTemplate, http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ func loadTemplates() {
|
||||
func respondAlert(w http.ResponseWriter, error, success, tplName string, status int) {
|
||||
w.WriteHeader(status)
|
||||
if tplName == "" {
|
||||
tplName = "index.tpl.html"
|
||||
tplName = config.IndexTemplate
|
||||
}
|
||||
templates[tplName].Execute(w, struct {
|
||||
Error string
|
||||
|
@ -3,7 +3,7 @@ package routes
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/schema"
|
||||
config2 "github.com/muety/wakapi/config"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type SettingsHandler struct {
|
||||
config *config2.Config
|
||||
config *conf.Config
|
||||
userSrvc *services.UserService
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ var credentialsDecoder = schema.NewDecoder()
|
||||
|
||||
func NewSettingsHandler(userService *services.UserService) *SettingsHandler {
|
||||
return &SettingsHandler{
|
||||
config: config2.Get(),
|
||||
config: conf.Get(),
|
||||
userSrvc: userService,
|
||||
}
|
||||
}
|
||||
@ -36,10 +36,10 @@ func (h *SettingsHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// TODO: when alerts are present, other data will not be passed to the template
|
||||
if handleAlerts(w, r, "settings.tpl.html") {
|
||||
if handleAlerts(w, r, conf.SettingsTemplate) {
|
||||
return
|
||||
}
|
||||
templates["settings.tpl.html"].Execute(w, data)
|
||||
templates[conf.SettingsTemplate].Execute(w, data)
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) PostCredentials(w http.ResponseWriter, r *http.Request) {
|
||||
@ -51,32 +51,32 @@ func (h *SettingsHandler) PostCredentials(w http.ResponseWriter, r *http.Request
|
||||
|
||||
var credentials models.CredentialsReset
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondAlert(w, "missing parameters", "", "settings.tpl.html", http.StatusBadRequest)
|
||||
respondAlert(w, "missing parameters", "", conf.SettingsTemplate, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := credentialsDecoder.Decode(&credentials, r.PostForm); err != nil {
|
||||
respondAlert(w, "missing parameters", "", "settings.tpl.html", http.StatusBadRequest)
|
||||
respondAlert(w, "missing parameters", "", conf.SettingsTemplate, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.CheckPasswordBcrypt(user, credentials.PasswordOld, h.config.Security.PasswordSalt) {
|
||||
respondAlert(w, "invalid credentials", "", "settings.tpl.html", http.StatusUnauthorized)
|
||||
respondAlert(w, "invalid credentials", "", conf.SettingsTemplate, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if !credentials.IsValid() {
|
||||
respondAlert(w, "invalid parameters", "", "settings.tpl.html", http.StatusBadRequest)
|
||||
respondAlert(w, "invalid parameters", "", conf.SettingsTemplate, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = credentials.PasswordNew
|
||||
if err := utils.HashPassword(user, h.config.Security.PasswordSalt); err != nil {
|
||||
respondAlert(w, "internal server error", "", "settings.tpl.html", http.StatusInternalServerError)
|
||||
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.userSrvc.Update(user); err != nil {
|
||||
respondAlert(w, "internal server error", "", "settings.tpl.html", http.StatusInternalServerError)
|
||||
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ func (h *SettingsHandler) PostCredentials(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
encoded, err := h.config.Security.SecureCookie.Encode(models.AuthCookieKey, login)
|
||||
if err != nil {
|
||||
respondAlert(w, "internal server error", "", "settings.tpl.html", http.StatusInternalServerError)
|
||||
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
if _, err := h.userSrvc.ResetApiKey(user); err != nil {
|
||||
respondAlert(w, "internal server error", "", "settings.tpl.html", http.StatusInternalServerError)
|
||||
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ func (h *SettingsHandler) PostToggleBadges(w http.ResponseWriter, r *http.Reques
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
|
||||
if _, err := h.userSrvc.ToggleBadges(user); err != nil {
|
||||
respondAlert(w, "internal server error", "", "settings.tpl.html", http.StatusInternalServerError)
|
||||
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
config2 "github.com/muety/wakapi/config"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
@ -10,13 +10,13 @@ import (
|
||||
|
||||
type SummaryHandler struct {
|
||||
summarySrvc *services.SummaryService
|
||||
config *config2.Config
|
||||
config *conf.Config
|
||||
}
|
||||
|
||||
func NewSummaryHandler(summaryService *services.SummaryService) *SummaryHandler {
|
||||
return &SummaryHandler{
|
||||
summarySrvc: summaryService,
|
||||
config: config2.Get(),
|
||||
config: conf.Get(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,13 +44,13 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
summary, err, status := h.loadUserSummary(r)
|
||||
if err != nil {
|
||||
respondAlert(w, err.Error(), "", "summary.tpl.html", status)
|
||||
respondAlert(w, err.Error(), "", conf.SummaryTemplate, status)
|
||||
return
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
if user == nil {
|
||||
respondAlert(w, "unauthorized", "", "summary.tpl.html", http.StatusUnauthorized)
|
||||
respondAlert(w, "unauthorized", "", conf.SummaryTemplate, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
||||
ApiKey: user.ApiKey,
|
||||
}
|
||||
|
||||
templates["summary.tpl.html"].Execute(w, vm)
|
||||
templates[conf.SummaryTemplate].Execute(w, vm)
|
||||
}
|
||||
|
||||
func (h *SummaryHandler) loadUserSummary(r *http.Request) (*models.Summary, error, int) {
|
||||
|
@ -3,7 +3,7 @@ import random
|
||||
import string
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
from typing import List, NoneType
|
||||
|
||||
import requests
|
||||
|
||||
@ -36,7 +36,7 @@ class Heartbeat:
|
||||
self.is_write: bool = is_write
|
||||
self.branch: str = branch
|
||||
self.type: str = type
|
||||
self.category: str = None
|
||||
self.category: str | NoneType = None
|
||||
|
||||
|
||||
def generate_data(n: int) -> List[Heartbeat]:
|
||||
@ -45,7 +45,7 @@ def generate_data(n: int) -> List[Heartbeat]:
|
||||
projects: List[str] = [randomword(random.randint(5, 10)) for _ in range(5)]
|
||||
languages: List[str] = list(LANGUAGES.keys())
|
||||
|
||||
for i in range(n):
|
||||
for _ in range(n):
|
||||
p: str = random.choice(projects)
|
||||
l: str = random.choice(languages)
|
||||
f: str = randomword(random.randint(2, 8))
|
||||
@ -77,7 +77,7 @@ def post_data_sync(data: List[Heartbeat], url: str):
|
||||
|
||||
def randomword(length: int) -> str:
|
||||
letters = string.ascii_lowercase
|
||||
return ''.join(random.choice(letters) for i in range(length))
|
||||
return ''.join(random.choice(letters) for _ in range(length))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
aggregateIntervalDays int = 1 // TODO: Make configurable
|
||||
aggregateIntervalDays int = 1
|
||||
)
|
||||
|
||||
type AggregationService struct {
|
||||
|
@ -37,6 +37,7 @@ type Interval struct {
|
||||
End time.Time
|
||||
}
|
||||
|
||||
// TODO: simplify!
|
||||
func (srv *SummaryService) Construct(from, to time.Time, user *models.User, recompute bool) (*models.Summary, error) {
|
||||
var existingSummaries []*models.Summary
|
||||
var cacheKey string
|
||||
|
@ -242,8 +242,8 @@ function equalizeHeights() {
|
||||
})
|
||||
}
|
||||
|
||||
function getTotal(data) {
|
||||
let total = data.reduce((acc, d) => acc + d.total, 0)
|
||||
function getTotal(items) {
|
||||
let total = items.reduce((acc, d) => acc + d.total, 0)
|
||||
document.getElementById('total-span').innerText = total.toString().toHHMMSS()
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
1.12.2
|
||||
1.12.3
|
@ -1,4 +1,5 @@
|
||||
<html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
<html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
<html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
@ -86,7 +87,7 @@
|
||||
<div class="flex flex-col mb-4">
|
||||
<div class="flex justify-between my-2">
|
||||
<div>
|
||||
<img class="with-url-src" src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=blue&label=today"/>
|
||||
<img class="with-url-src" src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=blue&label=today" alt="Shields.io badge"/>
|
||||
</div>
|
||||
<span class="with-url-inner text-xs bg-gray-900 rounded py-1 px-2 font-mono whitespace-no-wrap overflow-auto" style="max-width: 300px;">
|
||||
https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=blue&label=today
|
||||
@ -94,7 +95,7 @@
|
||||
</div>
|
||||
<div class="flex justify-between my-2">
|
||||
<div>
|
||||
<img class="with-url-src" src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=blue&label=last 30d"/>
|
||||
<img class="with-url-src" src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=blue&label=last 30d" alt="Shields.io badge"/>
|
||||
</div>
|
||||
<span class="with-url-inner text-xs bg-gray-900 rounded py-1 px-2 font-mono whitespace-no-wrap overflow-auto" style="max-width: 300px;">
|
||||
https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=blue&label=last 30d
|
||||
@ -104,7 +105,7 @@
|
||||
|
||||
<p>You can also add <span class="text-xs bg-gray-900 rounded py-1 px-2 font-mono">/project:your-cool-project</span> to the URL to filter by project.</p>
|
||||
{{ else }}
|
||||
<p>You have the ability to create badges from your coding statistics using <a href="https://shields.io" target="_blank" class="border-b border-green-800">Shields.io</a>. To do so, you need to grant public, unauthorized access to the respective endpoint.</p>
|
||||
<p>You have the ability to create badges from your coding statistics using <a href="https://shields.io" target="_blank" class="border-b border-green-800" rel="noopener noreferrer">Shields.io</a>. To do so, you need to grant public, unauthorized access to the respective endpoint.</p>
|
||||
<div class="flex justify-around mt-4">
|
||||
<span class="font-mono font-normal bg-gray-900 p-1 rounded whitespace-no-wrap">GET /api/compat/shields/v1</span>
|
||||
<button type="submit" class="py-1 px-2 rounded bg-green-700 hover:bg-green-800 text-white text-xs" title="Make endpoint public to enable badges">
|
||||
|
@ -1,4 +1,5 @@
|
||||
<html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
@ -19,9 +20,11 @@
|
||||
<p class="text-sm text-gray-300">
|
||||
💡 In order to use Wakapi, you need to create an account.
|
||||
After successful signup, you still need to set up the <a href="https://wakatime.com" target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="border-b border-green-700">WakaTime</a>
|
||||
client tools.
|
||||
Please refer to <a href="https://github.com/muety/wakapi#client-setup" target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="border-b border-green-700">this readme section</a> for instructions.
|
||||
You will be able to view you <strong>API Key</strong> once you log in.
|
||||
</p>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
@ -69,7 +70,7 @@
|
||||
</div>
|
||||
<canvas id="chart-projects"></canvas>
|
||||
<div class="hidden placeholder-container flex items-center justify-center h-full flex-col">
|
||||
<img src="assets/images/no_data.svg" class="w-20"/>
|
||||
<img src="assets/images/no_data.svg" class="w-20" alt="No data"/>
|
||||
<span class="text-sm mt-4">No data available ...</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -82,7 +83,7 @@
|
||||
</div>
|
||||
<canvas id="chart-os"></canvas>
|
||||
<div class="hidden placeholder-container flex items-center justify-center h-full flex-col">
|
||||
<img src="assets/images/no_data.svg" class="w-20"/>
|
||||
<img src="assets/images/no_data.svg" class="w-20" alt="No data"/>
|
||||
<span class="text-sm mt-4">No data available ...</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -95,7 +96,7 @@
|
||||
</div>
|
||||
<canvas id="chart-language"></canvas>
|
||||
<div class="hidden placeholder-container flex items-center justify-center h-full flex-col">
|
||||
<img src="assets/images/no_data.svg" class="w-20"/>
|
||||
<img src="assets/images/no_data.svg" class="w-20" alt="No data"/>
|
||||
<span class="text-sm mt-4">No data available ...</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -108,7 +109,7 @@
|
||||
</div>
|
||||
<canvas id="chart-editor"></canvas>
|
||||
<div class="hidden placeholder-container flex items-center justify-center h-full flex-col">
|
||||
<img src="assets/images/no_data.svg" class="w-20"/>
|
||||
<img src="assets/images/no_data.svg" class="w-20" alt="No data"/>
|
||||
<span class="text-sm mt-4">No data available ...</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -121,7 +122,7 @@
|
||||
</div>
|
||||
<canvas id="chart-machine"></canvas>
|
||||
<div class="hidden placeholder-container flex items-center justify-center h-full flex-col">
|
||||
<img src="assets/images/no_data.svg" class="w-20"/>
|
||||
<img src="assets/images/no_data.svg" class="w-20" alt="No data"/>
|
||||
<span class="text-sm mt-4">No data available ...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user