feat: roles SPA Users, Profile, API keys, Activity
This commit is contained in:
1 parent
3db490a615
commit
34dffe222f
98 files changed
+4957
-434
No files matched your search
@@ -33,8 +33,9 @@ type meAuthor struct {
|
||||
}
|
||||
|
||||
type meAccess struct {
|
||||
Role useCases.RoleRef `json:"role"`
|
||||
Permissions []role.Grant `json:"permissions"`
|
||||
Role useCases.RoleRef `json:"role"`
|
||||
Permissions []role.Grant `json:"permissions"`
|
||||
RoleAssignedBy *useCases.RoleAssigner `json:"role_assigned_by"`
|
||||
}
|
||||
|
||||
type meResponse struct {
|
||||
@@ -45,8 +46,9 @@ type meResponse struct {
|
||||
|
||||
func toMeAccess(access useCases.Access) meAccess {
|
||||
return meAccess{
|
||||
Role: useCases.NewRoleRef(access.Role),
|
||||
Permissions: access.Permissions,
|
||||
Role: useCases.NewRoleRef(access.Role),
|
||||
Permissions: access.Permissions,
|
||||
RoleAssignedBy: access.RoleAssignedBy,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"RenewCMS/api/pkg/claims"
|
||||
validator "RenewCMS/api/pkg/validator"
|
||||
"RenewCMS/internal/domain/invite"
|
||||
"RenewCMS/internal/domain/role"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"encoding/json"
|
||||
@@ -50,14 +49,15 @@ type inviteListItem struct {
|
||||
}
|
||||
|
||||
type userListItem struct {
|
||||
ID uint32 `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Email string `json:"email"`
|
||||
Role *useCases.RoleRef `json:"role"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID uint32 `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Email string `json:"email"`
|
||||
Role *useCases.RoleRef `json:"role"`
|
||||
RoleAssignedBy *useCases.RoleAssigner `json:"role_assigned_by"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func NewInviteHandler(createInvite *useCases.CreateInviteUseCase, cancelInvite *useCases.CancelInviteUseCase, renewInvite *useCases.RenewInviteUseCase, listUsersAndInvites *useCases.ListUsersAndInvitesUseCase) *InviteHandler {
|
||||
@@ -160,22 +160,6 @@ func (h *InviteHandler) RenewInvite(w http.ResponseWriter, r *http.Request) {
|
||||
writeInviteResponse(w, http.StatusOK, renewed)
|
||||
}
|
||||
|
||||
func writeRoleRefusal(w http.ResponseWriter, err error) bool {
|
||||
var escalation role.EscalationError
|
||||
switch {
|
||||
case errors.As(err, &escalation):
|
||||
w.Header().Set(apperr.EscalationHeader, escalation.Key)
|
||||
http.Error(w, apperr.EscalationErrorMessage, http.StatusForbidden)
|
||||
case errors.Is(err, role.ErrRoleLocked):
|
||||
http.Error(w, apperr.RoleLockedErrorMessage, http.StatusForbidden)
|
||||
case errors.Is(err, role.ErrUnknownRole):
|
||||
http.Error(w, apperr.InvalidRoleErrorMessage, http.StatusBadRequest)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func writeManageInviteError(w http.ResponseWriter, err error) {
|
||||
if writeRoleRefusal(w, err) {
|
||||
return
|
||||
@@ -200,8 +184,10 @@ func writeCreateInviteError(w http.ResponseWriter, err error) {
|
||||
}
|
||||
switch {
|
||||
case errors.Is(err, invite.ErrEmailHasAccount):
|
||||
w.Header().Set(apperr.ErrorCodeHeader, apperr.EmailHasAccountErrorCode)
|
||||
http.Error(w, apperr.EmailHasAccountErrorMessage, http.StatusConflict)
|
||||
case errors.Is(err, invite.ErrPendingInvite):
|
||||
w.Header().Set(apperr.ErrorCodeHeader, apperr.InvitePendingErrorCode)
|
||||
http.Error(w, apperr.PendingInviteErrorMessage, http.StatusConflict)
|
||||
case errors.Is(err, invite.ErrInviteEmailNotSent):
|
||||
http.Error(w, apperr.InviteNotSentErrorMessage, http.StatusInternalServerError)
|
||||
@@ -222,7 +208,14 @@ func (h *InviteHandler) ListUsers(w http.ResponseWriter, _ *http.Request) {
|
||||
items := make([]any, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.Kind == useCases.EntryKindInvite {
|
||||
items = append(items, inviteListItem{entry.ID, entry.Kind, entry.Email, entry.Role, entry.Status, entry.CreatedAt})
|
||||
items = append(items, inviteListItem{
|
||||
ID: entry.ID,
|
||||
Kind: entry.Kind,
|
||||
Email: entry.Email,
|
||||
Role: entry.Role,
|
||||
Status: entry.Status,
|
||||
CreatedAt: entry.CreatedAt,
|
||||
})
|
||||
continue
|
||||
}
|
||||
items = append(items, userListItemOf(entry))
|
||||
|
||||
@@ -27,21 +27,6 @@ func NewRoleHandler(changeRole *useCases.ChangeUserRoleUseCase) *RoleHandler {
|
||||
return &RoleHandler{ChangeUserRoleUseCase: changeRole}
|
||||
}
|
||||
|
||||
func userListItemOf(entry useCases.DirectoryEntry) userListItem {
|
||||
return userListItem{entry.ID, entry.Kind, entry.Username, entry.DisplayName, entry.Email, entry.Role, entry.Status, entry.CreatedAt}
|
||||
}
|
||||
|
||||
func writeNoStoreJSON(w http.ResponseWriter, status int, payload any) {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
|
||||
func (h *RoleHandler) Change(w http.ResponseWriter, r *http.Request) {
|
||||
targetID, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
"RenewCMS/internal/domain/role"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func userListItemOf(entry useCases.DirectoryEntry) userListItem {
|
||||
return userListItem{
|
||||
ID: entry.ID,
|
||||
Kind: entry.Kind,
|
||||
Username: entry.Username,
|
||||
DisplayName: entry.DisplayName,
|
||||
Email: entry.Email,
|
||||
Role: entry.Role,
|
||||
RoleAssignedBy: entry.RoleAssignedBy,
|
||||
Status: entry.Status,
|
||||
CreatedAt: entry.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func writeNoStoreJSON(w http.ResponseWriter, status int, payload any) {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
|
||||
func writeRoleRefusal(w http.ResponseWriter, err error) bool {
|
||||
var escalation role.EscalationError
|
||||
switch {
|
||||
case errors.As(err, &escalation):
|
||||
w.Header().Set(apperr.EscalationHeader, escalation.Key)
|
||||
http.Error(w, apperr.EscalationErrorMessage, http.StatusForbidden)
|
||||
case errors.Is(err, role.ErrRoleLocked):
|
||||
http.Error(w, apperr.RoleLockedErrorMessage, http.StatusForbidden)
|
||||
case errors.Is(err, role.ErrUnknownRole):
|
||||
http.Error(w, apperr.InvalidRoleErrorMessage, http.StatusBadRequest)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in new issue
Block a user