Skip to content

Commit 24b34be

Browse files
committedSep 26, 2023
feat(wip): first draft of projects page implementation [skip ci]
1 parent 793d9e0 commit 24b34be

16 files changed

+253
-74
lines changed
 

‎config/templates.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ const (
1010
SettingsTemplate = "settings.tpl.html"
1111
SummaryTemplate = "summary.tpl.html"
1212
LeaderboardTemplate = "leaderboard.tpl.html"
13+
ProjectsTemplate = "projects.tpl.html"
1314
)

‎main.go

+2
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ func main() {
227227
settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, summaryService, aliasService, aggregationService, languageMappingService, projectLabelService, keyValueService, mailService)
228228
subscriptionHandler := routes.NewSubscriptionHandler(userService, mailService, keyValueService)
229229
leaderboardHandler := routes.NewLeaderboardHandler(userService, leaderboardService)
230+
projectsHandler := routes.NewProjectsHandler(userService, heartbeatService)
230231
homeHandler := routes.NewHomeHandler(keyValueService)
231232
loginHandler := routes.NewLoginHandler(userService, mailService)
232233
imprintHandler := routes.NewImprintHandler(keyValueService)
@@ -269,6 +270,7 @@ func main() {
269270
imprintHandler.RegisterRoutes(rootRouter)
270271
summaryHandler.RegisterRoutes(rootRouter)
271272
leaderboardHandler.RegisterRoutes(rootRouter)
273+
projectsHandler.RegisterRoutes(rootRouter)
272274
settingsHandler.RegisterRoutes(rootRouter)
273275
subscriptionHandler.RegisterRoutes(rootRouter)
274276
relayHandler.RegisterRoutes(rootRouter)

‎models/view/leaderboard.go

+1-31
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package view
33
import (
44
"github.com/muety/wakapi/models"
55
"github.com/muety/wakapi/utils"
6-
"strings"
76
"time"
87
)
98

@@ -46,36 +45,7 @@ func (s *LeaderboardViewModel) ColorModifier(item *models.LeaderboardItemRanked,
4645
}
4746

4847
func (s *LeaderboardViewModel) LangIcon(lang string) string {
49-
// https://icon-sets.iconify.design/mdi/
50-
langs := map[string]string{
51-
"c++": "language-cpp",
52-
"cpp": "language-cpp",
53-
"go": "language-go",
54-
"haskell": "language-haskell",
55-
"html": "language-html5",
56-
"java": "language-java",
57-
"javascript": "language-javascript",
58-
"jsx": "language-javascript",
59-
"kotlin": "language-kotlin",
60-
"lua": "language-lua",
61-
"php": "language-php",
62-
"python": "language-python",
63-
"r": "language-r",
64-
"ruby": "language-ruby",
65-
"rust": "language-rust",
66-
"swift": "language-swift",
67-
"typescript": "language-typescript",
68-
"tsx": "language-typescript",
69-
"markdown": "language-markdown",
70-
"vue": "vuejs",
71-
"react": "react",
72-
"bash": "bash",
73-
"json": "code-json",
74-
}
75-
if match, ok := langs[strings.ToLower(lang)]; ok {
76-
return "mdi:" + match
77-
}
78-
return ""
48+
return GetLanguageIcon(lang)
7949
}
8050

8151
func (s *LeaderboardViewModel) LastUpdate() time.Time {

‎models/view/projects.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package view
2+
3+
import (
4+
"github.com/muety/wakapi/models"
5+
"github.com/muety/wakapi/utils"
6+
)
7+
8+
type ProjectsViewModel struct {
9+
Messages
10+
User *models.User
11+
Projects []*models.ProjectStats
12+
ApiKey string
13+
PageParams *utils.PageParams
14+
}
15+
16+
func (s *ProjectsViewModel) LangIcon(lang string) string {
17+
return GetLanguageIcon(lang)
18+
}
19+
20+
func (s *ProjectsViewModel) WithSuccess(m string) *ProjectsViewModel {
21+
s.SetSuccess(m)
22+
return s
23+
}
24+
25+
func (s *ProjectsViewModel) WithError(m string) *ProjectsViewModel {
26+
s.SetError(m)
27+
return s
28+
}

‎models/view/utils.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package view
2+
3+
import "strings"
4+
5+
func GetLanguageIcon(language string) string {
6+
// https://icon-sets.iconify.design/mdi/
7+
langs := map[string]string{
8+
"c++": "language-cpp",
9+
"cpp": "language-cpp",
10+
"go": "language-go",
11+
"haskell": "language-haskell",
12+
"html": "language-html5",
13+
"java": "language-java",
14+
"javascript": "language-javascript",
15+
"jsx": "language-javascript",
16+
"kotlin": "language-kotlin",
17+
"lua": "language-lua",
18+
"php": "language-php",
19+
"python": "language-python",
20+
"r": "language-r",
21+
"ruby": "language-ruby",
22+
"rust": "language-rust",
23+
"swift": "language-swift",
24+
"typescript": "language-typescript",
25+
"tsx": "language-typescript",
26+
"markdown": "language-markdown",
27+
"vue": "vuejs",
28+
"react": "react",
29+
"bash": "bash",
30+
"json": "code-json",
31+
}
32+
if match, ok := langs[strings.ToLower(language)]; ok {
33+
return "mdi:" + match
34+
}
35+
return ""
36+
}

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"compress": "brotli -f static/assets/css/*.dist.css && brotli -f static/assets/js/*.dist.js"
1010
},
1111
"devDependencies": {
12-
"@iconify/json": "^2.1.136",
12+
"@iconify/json": "^2.2.120",
1313
"@iconify/json-tools": "^1.0.10",
1414
"chokidar-cli": "^3.0.0",
1515
"tailwindcss": "^3.1.8"

‎routes/projects.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package routes
2+
3+
import (
4+
"github.com/emvi/logbuch"
5+
"github.com/go-chi/chi/v5"
6+
conf "github.com/muety/wakapi/config"
7+
"github.com/muety/wakapi/middlewares"
8+
"github.com/muety/wakapi/models"
9+
"github.com/muety/wakapi/models/view"
10+
routeutils "github.com/muety/wakapi/routes/utils"
11+
"github.com/muety/wakapi/services"
12+
"github.com/muety/wakapi/utils"
13+
"net/http"
14+
"time"
15+
)
16+
17+
type ProjectsHandler struct {
18+
config *conf.Config
19+
userService services.IUserService
20+
heartbeatService services.IHeartbeatService
21+
}
22+
23+
func NewProjectsHandler(userService services.IUserService, heartbeatService services.IHeartbeatService) *ProjectsHandler {
24+
return &ProjectsHandler{
25+
config: conf.Get(),
26+
userService: userService,
27+
heartbeatService: heartbeatService,
28+
}
29+
}
30+
31+
func (h *ProjectsHandler) RegisterRoutes(router chi.Router) {
32+
r := chi.NewRouter()
33+
r.Use(
34+
middlewares.NewAuthenticateMiddleware(h.userService).
35+
WithRedirectTarget(defaultErrorRedirectTarget()).
36+
WithRedirectErrorMessage("unauthorized").Handler,
37+
)
38+
r.Get("/", h.GetIndex)
39+
40+
router.Mount("/projects", r)
41+
}
42+
43+
func (h *ProjectsHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
44+
if h.config.IsDev() {
45+
loadTemplates()
46+
}
47+
if err := templates[conf.ProjectsTemplate].Execute(w, h.buildViewModel(r, w)); err != nil {
48+
logbuch.Error(err.Error())
49+
}
50+
}
51+
52+
func (h *ProjectsHandler) buildViewModel(r *http.Request, w http.ResponseWriter) *view.ProjectsViewModel {
53+
user := middlewares.GetPrincipal(r)
54+
pageParams := utils.ParsePageParamsWithDefault(r, 1, 100)
55+
// note: pagination is not fully implemented, yet
56+
// count function to get total item / total pages is missing
57+
// and according ui (+ optionally search bar) is missing, too
58+
59+
var err error
60+
var projects []*models.ProjectStats
61+
62+
projects, err = h.heartbeatService.GetUserProjectStats(user, time.Time{}, utils.BeginOfToday(time.Local), false)
63+
if err != nil {
64+
conf.Log().Request(r).Error("error while fetching project stats for '%s' - %v", user.ID, err)
65+
return &view.ProjectsViewModel{
66+
Messages: view.Messages{Error: criticalError},
67+
}
68+
}
69+
70+
var apiKey string
71+
if user != nil {
72+
apiKey = user.ApiKey
73+
}
74+
75+
vm := &view.ProjectsViewModel{
76+
User: user,
77+
Projects: projects,
78+
ApiKey: apiKey,
79+
PageParams: pageParams,
80+
}
81+
return routeutils.WithSessionMessages(vm, r, w)
82+
}

‎scripts/bundle_icons.js

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ let icons = [
7979
'twemoji:frowning-face',
8080
'ci:dot-03-m',
8181
'jam:crown-f',
82+
'octicon:project-16',
8283
]
8384

8485
const output = path.normalize(path.join(__dirname, '../static/assets/js/icons.dist.js'))

‎static/assets/css/app.css

+4
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,8 @@ body {
150150
max-width: -moz-available;
151151
max-width: -webkit-fill-available;
152152
max-width: fill-available;
153+
}
154+
155+
.projects-item a {
156+
@apply flex flex-col align-middle bg-gray-800 hover:bg-gray-850 py-2 px-4 rounded rounded-md shadow-md hover:shadow-lg;
153157
}

‎static/assets/css/app.dist.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎static/assets/css/app.dist.css.br

64 Bytes
Binary file not shown.

‎static/assets/js/icons.dist.js

+25-24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎static/assets/js/icons.dist.js.br

424 Bytes
Binary file not shown.

‎views/menu-main.tpl.html

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
<span class="text-gray-300 hidden lg:inline-block">Leaderboard</span>
1616
</a>
1717

18+
<a class="menu-item" href="projects">
19+
<span class="iconify inline text-2xl text-gray-400 p-px" data-icon="octicon:project-16"></span>
20+
<span class="text-gray-300 hidden lg:inline-block">Projects</span>
21+
</a>
22+
1823
<div class="menu-item hidden sm:flex imp:cursor-not-allowed">
1924
<span class="iconify inline text-2xl text-gray-700" data-icon="bi:people-fill"></span>
2025
<a class="text-gray-600 leading-none hidden lg:inline-block">Team<br>

‎views/projects.tpl.html

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
{{ template "head.tpl.html" . }}
5+
6+
<body class="relative bg-gray-900 text-gray-700 p-4 pt-10 flex flex-col min-h-screen {{ if .User }} max-w-screen-xl {{ else }} max-w-screen-lg {{end}} mx-auto justify-center">
7+
8+
{{ template "alerts.tpl.html" . }}
9+
10+
{{ template "menu-main.tpl.html" . }}
11+
12+
<main class="mt-10 grow flex justify-center w-full" id="projects-page">
13+
<div class="flex flex-col grow mt-10 max-available">
14+
<h1 class="h1" style="margin-bottom: 0.5rem">Your Projects</h1>
15+
16+
<p class="block text-sm text-gray-300 w-full lg:w-3/4 mb-8">
17+
This is an overview of all your projects.
18+
</p>
19+
20+
<ul class="inline-grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 mt-4 text-gray-300">
21+
{{ range $i, $project := .Projects }}
22+
<li class="projects-item">
23+
<a href="#">
24+
<span class="text-lg font-semibold truncate">{{ $project.Project }}
25+
{{ if $.LangIcon $project.TopLanguage }}
26+
<span class="align-middle leading-none"><span class="iconify inline text-white text-lg ml-1" data-icon="{{ $.LangIcon $project.TopLanguage | urlSafe }}"></span></span>
27+
{{ else if $project.TopLanguage }}
28+
<span class="text-sm">({{ $project.TopLanguage }})</span>
29+
{{ end }}
30+
</span>
31+
<small>{{ $project.First.T | datetime }} – {{ $project.Last.T | datetime }}</small>
32+
</a>
33+
</li>
34+
{{ end }}
35+
</ul>
36+
</div>
37+
</main>
38+
39+
{{ template "footer.tpl.html" . }}
40+
41+
{{ template "foot.tpl.html" . }}
42+
</body>
43+
44+
</html>

‎yarn.lock

+22-17
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44

55
"@iconify/json-tools@^1.0.10":
66
version "1.0.10"
7-
resolved "https://registry.npmjs.org/@iconify/json-tools/-/json-tools-1.0.10.tgz"
7+
resolved "https://registry.yarnpkg.com/@iconify/json-tools/-/json-tools-1.0.10.tgz#d9a7050dbbe8bb29d684d4b3f9446ed2d0bea3cc"
88
integrity sha512-LFelJDOLZ6JHlmlAkgrvmcu4hpNPB91KYcr4f60D/exzU1eNOb4/KCVHIydGHIQFaOacIOD+Xy+B7P1z812cZg==
99

10-
"@iconify/json@^2.1.136":
11-
version "2.1.136"
12-
resolved "https://registry.npmjs.org/@iconify/json/-/json-2.1.136.tgz"
13-
integrity sha512-tO5hV+yXn87+OCQqiVzis6i4YQiRX4044ZjubP6GmbeclE6tsypK+by/tXjbm90GTX0jhsOJ6YLzWl3szivywg==
10+
"@iconify/json@^2.2.120":
11+
version "2.2.120"
12+
resolved "https://registry.yarnpkg.com/@iconify/json/-/json-2.2.120.tgz#fb2d2bb1b4267236d781cd00d81afcca91fdb0b2"
13+
integrity sha512-vxE3fNGgQEEu2nvMuR/g4Cu/CxPpQWjc3cwsk/KY3iTN4hLKL0lnBsi1GmQL1ITSsyib2f72h329j+D9S9esbg==
1414
dependencies:
1515
"@iconify/types" "*"
16-
pathe "^0.3.0"
16+
pathe "^1.1.0"
1717

1818
"@iconify/types@*":
1919
version "2.0.0"
@@ -28,7 +28,7 @@
2828
"@nodelib/fs.stat" "2.0.5"
2929
run-parallel "^1.1.9"
3030

31-
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
31+
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
3232
version "2.0.5"
3333
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
3434
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -148,16 +148,16 @@ color-convert@^1.9.0:
148148
dependencies:
149149
color-name "1.1.3"
150150

151-
color-name@^1.1.4:
152-
version "1.1.4"
153-
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
154-
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
155-
156151
color-name@1.1.3:
157152
version "1.1.3"
158153
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
159154
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
160155

156+
color-name@^1.1.4:
157+
version "1.1.4"
158+
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
159+
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
160+
161161
cssesc@^3.0.0:
162162
version "3.0.0"
163163
resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
@@ -229,6 +229,11 @@ find-up@^3.0.0:
229229
dependencies:
230230
locate-path "^3.0.0"
231231

232+
fsevents@~2.3.2:
233+
version "2.3.3"
234+
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
235+
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
236+
232237
function-bind@^1.1.1:
233238
version "1.1.1"
234239
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
@@ -381,10 +386,10 @@ path-parse@^1.0.7:
381386
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
382387
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
383388

384-
pathe@^0.3.0:
385-
version "0.3.9"
386-
resolved "https://registry.npmjs.org/pathe/-/pathe-0.3.9.tgz"
387-
integrity sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g==
389+
pathe@^1.1.0:
390+
version "1.1.1"
391+
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a"
392+
integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==
388393

389394
picocolors@^1.0.0:
390395
version "1.0.0"
@@ -445,7 +450,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
445450
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
446451
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
447452

448-
postcss@^8.0.0, postcss@^8.2.14, postcss@^8.3.3, postcss@^8.4.18, postcss@>=8.0.9:
453+
postcss@^8.4.18:
449454
version "8.4.19"
450455
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz"
451456
integrity sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==

0 commit comments

Comments
 (0)
Please sign in to comment.