1
+ package io.tohuwabohu.kamifusen
2
+
3
+ import io.quarkus.logging.Log
4
+ import io.smallrye.mutiny.Uni
5
+ import io.tohuwabohu.kamifusen.crud.ApiUser
6
+ import io.tohuwabohu.kamifusen.crud.ApiUserRepository
7
+ import io.tohuwabohu.kamifusen.crud.PageRepository
8
+ import io.tohuwabohu.kamifusen.crud.dto.PageVisitDtoRepository
9
+ import io.tohuwabohu.kamifusen.crud.security.*
10
+ import io.tohuwabohu.kamifusen.ssr.*
11
+ import io.tohuwabohu.kamifusen.ssr.response.recoverWithHtmxResponse
12
+ import io.vertx.ext.web.RoutingContext
13
+ import jakarta.annotation.security.RolesAllowed
14
+ import jakarta.ws.rs.*
15
+ import jakarta.ws.rs.core.*
16
+ import org.eclipse.microprofile.config.inject.ConfigProperty
17
+ import java.time.Instant
18
+ import java.time.LocalDateTime
19
+ import java.util.*
20
+
21
+
22
+ @Path(" /" )
23
+ class AppAdminResource (
24
+ private val apiUserRepository : ApiUserRepository ,
25
+ private val pageVisitDtoRepository : PageVisitDtoRepository ,
26
+ private val pageRepository : PageRepository
27
+ ) {
28
+ @ConfigProperty(name = " quarkus.http.auth.form.cookie-name" )
29
+ lateinit var cookieName: String
30
+
31
+ @Path(" /fragment/register" )
32
+ @POST
33
+ @Consumes(MediaType .APPLICATION_FORM_URLENCODED )
34
+ @Produces(MediaType .TEXT_HTML )
35
+ @RolesAllowed(" app-admin" )
36
+ fun registerAdmin (
37
+ @FormParam(" password" ) password : String ,
38
+ @FormParam(" password-confirm" ) passwordConfirm : String
39
+ ): Uni <Response > =
40
+ validatePassword(password, passwordConfirm).flatMap { result ->
41
+ if (result == PasswordValidation .VALID ) {
42
+ apiUserRepository.setAdminPassword(password)
43
+ .map { Response .ok(renderPasswordFlow(result)).build() }
44
+ } else {
45
+ Uni .createFrom().item(Response .ok(renderPasswordFlow(result)).build())
46
+ }
47
+ }.onFailure().invoke { e -> Log .error(" Error during admin password update." , e) }
48
+ .onFailure().recoverWithHtmxResponse(Response .Status .INTERNAL_SERVER_ERROR )
49
+
50
+ @Path(" /logout" )
51
+ @GET
52
+ @Produces(MediaType .TEXT_PLAIN )
53
+ fun logoutAdmin (@Context routingContext : RoutingContext ): Uni <Response > =
54
+ Uni .createFrom().item(
55
+ Response .status(Response .Status .FOUND )
56
+ .cookie(
57
+ NewCookie .Builder (cookieName)
58
+ .maxAge(0 )
59
+ .expiry(Date .from(Instant .EPOCH ))
60
+ .path(" /" )
61
+ .build()
62
+ )
63
+ .header(" Location" , " /index.html" )
64
+ .build()
65
+ )
66
+
67
+ @Path(" /dashboard" )
68
+ @GET
69
+ @Consumes(MediaType .TEXT_PLAIN )
70
+ @Produces(MediaType .TEXT_HTML )
71
+ @RolesAllowed(" app-admin" )
72
+ fun renderAdminDashboard (@Context securityContext : SecurityContext ): Uni <Response > =
73
+ apiUserRepository.findByUsername(securityContext.userPrincipal.name).map { adminUser ->
74
+ Response .ok(renderAdminPage(" Dashboard" , isFirstTimeSetup = adminUser!! .updated == null ) {
75
+ dashboard(adminUser)
76
+ }).build()
77
+ }.onFailure().invoke { e -> Log .error(" Error during dashboard rendering." , e) }
78
+ .onFailure().recoverWithHtmxResponse(Response .Status .INTERNAL_SERVER_ERROR )
79
+
80
+ @Path(" /stats" )
81
+ @GET
82
+ @Consumes(MediaType .TEXT_PLAIN )
83
+ @Produces(MediaType .TEXT_HTML )
84
+ @RolesAllowed(" app-admin" )
85
+ fun renderVisits (): Uni <Response > =
86
+ pageVisitDtoRepository.getAllPageVisits()
87
+ .flatMap {
88
+ Uni .createFrom().item(Response .ok(renderAdminPage(" Stats" ) {
89
+ stats(it)
90
+ }).build())
91
+ }
92
+ .onFailure().invoke { e -> Log .error(" Error during stats rendering." , e) }
93
+ .onFailure().recoverWithHtmxResponse(Response .Status .INTERNAL_SERVER_ERROR )
94
+
95
+ @Path(" /pages" )
96
+ @GET
97
+ @Consumes(MediaType .TEXT_PLAIN )
98
+ @Produces(MediaType .TEXT_HTML )
99
+ @RolesAllowed(" app-admin" )
100
+ fun renderPageList (): Uni <Response > =
101
+ pageRepository.listAllPages().flatMap {
102
+ Uni .createFrom().item(Response .ok(renderAdminPage(" Pages" ) {
103
+ pages(it)
104
+ }).build())
105
+ }.onFailure().invoke { e -> Log .error(" Error during pages rendering." , e) }
106
+ .onFailure().recoverWithHtmxResponse(Response .Status .INTERNAL_SERVER_ERROR )
107
+
108
+ @Path(" /users" )
109
+ @GET
110
+ @Consumes(MediaType .TEXT_PLAIN )
111
+ @Produces(MediaType .TEXT_HTML )
112
+ @RolesAllowed(" app-admin" )
113
+ fun renderUserList (): Uni <Response > =
114
+ apiUserRepository.listAll().flatMap {
115
+ Uni .createFrom().item(Response .ok(renderAdminPage(" Users" ) {
116
+ users(it)
117
+ }).build())
118
+ }.onFailure().invoke { e -> Log .error(" Error during user list rendering." , e) }
119
+ .onFailure().recoverWithHtmxResponse(Response .Status .INTERNAL_SERVER_ERROR )
120
+
121
+ @Path(" /fragment/keygen" )
122
+ @POST
123
+ @Consumes(MediaType .APPLICATION_FORM_URLENCODED )
124
+ @Produces(MediaType .TEXT_PLAIN )
125
+ @RolesAllowed(" app-admin" )
126
+ fun renderNewApiKey (
127
+ @FormParam(" username" ) username : String ,
128
+ @FormParam(" role" ) role : String ,
129
+ @FormParam(" expiresAt" ) expiresAt : String
130
+ ): Uni <Response > =
131
+ validateUser(username, apiUserRepository).flatMap { result ->
132
+ if (result == UserValidation .VALID ) {
133
+ apiUserRepository.addUser(
134
+ ApiUser (
135
+ username = username,
136
+ role = role,
137
+ expiresAt = when (expiresAt) {
138
+ " " -> null
139
+ else -> LocalDateTime .parse(expiresAt)
140
+ },
141
+ )
142
+ ).map { keyRaw -> Response .ok(renderCreatedApiKey(keyRaw)).build() }
143
+ } else {
144
+ Uni .createFrom().item(Response
145
+ .ok(renderUsernameValidationError(result))
146
+ .header(" hx-retarget" , " #username" )
147
+ .build())
148
+ }
149
+ }.onFailure().invoke { e -> Log .error(" Error during keygen." , e) }
150
+ .onFailure().recoverWithItem(Response .status(Response .Status .INTERNAL_SERVER_ERROR ).build())
151
+
152
+ @Path(" /fragment/retire/{userId}" )
153
+ @POST
154
+ @Produces(MediaType .TEXT_HTML )
155
+ @RolesAllowed(" app-admin" )
156
+ fun retireApiKey (userId : UUID ): Uni <Response > =
157
+ apiUserRepository.expireUser(userId).onItem()
158
+ .transform { Response .ok().header(" hx-redirect" , " /users" ).build() }
159
+ .onFailure().invoke { e -> Log .error(" Error during key retirement" , e) }
160
+ .onFailure().recoverWithItem(Response .serverError().build())
161
+
162
+ @Path(" /fragment/pageadd" )
163
+ @POST
164
+ @Consumes(MediaType .APPLICATION_FORM_URLENCODED )
165
+ @Produces(MediaType .TEXT_PLAIN )
166
+ @RolesAllowed(" app-admin" )
167
+ fun registerNewPage (@FormParam(" path" ) path : String , @FormParam(" domain" ) domain : String ): Uni <Response > =
168
+ validatePage(path, domain, pageRepository).flatMap { result ->
169
+ if (result == PageValidation .VALID ) {
170
+ pageRepository.addPage(path, domain).map { Response .ok().header(" hx-redirect" , " /pages" ).build() }
171
+ } else {
172
+ Uni .createFrom().item(Response
173
+ .ok(renderPageValidationError(result))
174
+ .header(" hx-retarget" , " #path" )
175
+ .build())
176
+ }
177
+ }.onFailure().invoke { e -> Log .error(" Error during page registration." , e) }
178
+ .onFailure().recoverWithItem(Response .serverError().build())
179
+
180
+ @Path(" /fragment/pagedel/{pageId}" )
181
+ @POST
182
+ @Consumes(MediaType .APPLICATION_FORM_URLENCODED )
183
+ @Produces(MediaType .TEXT_PLAIN )
184
+ @RolesAllowed(" app-admin" )
185
+ fun unregisterPage (pageId : UUID ): Uni <Response > =
186
+ pageRepository.deletePage(pageId).map { Response .ok().header(" hx-redirect" , " /pages" ).build() }
187
+ .onFailure().invoke { e -> Log .error(" Error during page registration." , e) }
188
+ .onFailure().recoverWithItem(Response .serverError().build())
189
+ }
0 commit comments