feat(users): add admin-granted verified badge for trusted contributors

- migration 0002: users.verified boolean, default false
- verified rides in the JWT (role + verified claims)
- POST /users/{id}/verify + /unverify, admin-only (403 otherwise)
- Issue now takes the whole user so trust claims travel in the token
- unit + integration + admin-flow tests
This commit is contained in:
Efremenko Arhip
2026-07-26 19:10:01 +03:00
parent 0c1f5f06d4
commit c6a66747eb
20 changed files with 429 additions and 51 deletions
+17 -8
View File
@@ -13,19 +13,21 @@ import (
// UseCases bundles the application services the handlers drive.
type UseCases struct {
Register *usecase.Register
Login *usecase.Login
Users usecase.UserRepository
Register *usecase.Register
Login *usecase.Login
SetVerified *usecase.SetVerified
Users usecase.UserRepository
}
// NewServer wires the routes and the middleware stack and returns the handler.
// The issuer verifies tokens for the protected /me route.
// The issuer verifies tokens for the JWT-protected routes.
func NewServer(log *slog.Logger, uc UseCases, issuer auth.Issuer) http.Handler {
h := &Handlers{
register: uc.Register,
login: uc.Login,
users: uc.Users,
log: log,
register: uc.Register,
login: uc.Login,
setVerified: uc.SetVerified,
users: uc.Users,
log: log,
}
mux := http.NewServeMux()
@@ -36,6 +38,13 @@ func NewServer(log *slog.Logger, uc UseCases, issuer auth.Issuer) http.Handler {
// /me proves a token round-trips; it sits behind JWT auth.
mux.Handle("GET /me", chain(http.HandlerFunc(h.handleMe), withJWT(issuer)))
// Admin-only: grant or revoke the trusted-contributor badge. withAdmin sits
// inside withJWT so the role is available from the verified token.
mux.Handle("POST /users/{id}/verify",
chain(h.handleSetVerified(true), withJWT(issuer), withAdmin))
mux.Handle("POST /users/{id}/unverify",
chain(h.handleSetVerified(false), withJWT(issuer), withAdmin))
// Outermost first: every request gets an ID and an access-log line.
return chain(mux, withRequestID, withAccessLog(log))
}