205 lines
6.2 KiB
Go
205 lines
6.2 KiB
Go
package user
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
"RenewCMS/api/pkg/claims"
|
|
validator "RenewCMS/api/pkg/validator"
|
|
"RenewCMS/internal/domain/invite"
|
|
"RenewCMS/internal/domain/user"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type InviteHandler struct {
|
|
CreateInviteUseCase *useCases.CreateInviteUseCase
|
|
CancelInviteUseCase *useCases.CancelInviteUseCase
|
|
RenewInviteUseCase *useCases.RenewInviteUseCase
|
|
ListUsersAndInvitesUseCase *useCases.ListUsersAndInvitesUseCase
|
|
}
|
|
|
|
type CreateInviteRequest struct {
|
|
Email string `json:"email" validate:"required,inviteemail"`
|
|
}
|
|
|
|
type inviteResponse struct {
|
|
ID uint32 `json:"id"`
|
|
Kind string `json:"kind"`
|
|
Email string `json:"email"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
|
|
type inviteListItem struct {
|
|
ID uint32 `json:"id"`
|
|
Kind string `json:"kind"`
|
|
Email string `json:"email"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
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 string `json:"role"`
|
|
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 {
|
|
return &InviteHandler{
|
|
CreateInviteUseCase: createInvite,
|
|
CancelInviteUseCase: cancelInvite,
|
|
RenewInviteUseCase: renewInvite,
|
|
ListUsersAndInvitesUseCase: listUsersAndInvites,
|
|
}
|
|
}
|
|
|
|
func (h *InviteHandler) CreateInvite(w http.ResponseWriter, r *http.Request) {
|
|
var request CreateInviteRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := validator.Validate.Struct(request); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
userId, ok := claims.UserId(r)
|
|
if !ok {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
created, err := h.CreateInviteUseCase.CreateInvite(r.Context(), useCases.CreateInviteCommand{
|
|
Email: request.Email,
|
|
InvitedBy: userId,
|
|
})
|
|
if err != nil {
|
|
writeCreateInviteError(w, err)
|
|
return
|
|
}
|
|
|
|
writeInviteResponse(w, http.StatusCreated, created)
|
|
}
|
|
|
|
func writeInviteResponse(w http.ResponseWriter, status int, entry invite.Invite) {
|
|
body, err := json.Marshal(inviteResponse{
|
|
ID: entry.ID,
|
|
Kind: useCases.EntryKindInvite,
|
|
Email: entry.Email,
|
|
Status: string(entry.StatusAt(time.Now())),
|
|
CreatedAt: entry.CreatedAt,
|
|
ExpiresAt: entry.ExpiresAt,
|
|
})
|
|
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 parseInviteId(r *http.Request) (uint32, error) {
|
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
return uint32(id), err
|
|
}
|
|
|
|
func (h *InviteHandler) CancelInvite(w http.ResponseWriter, r *http.Request) {
|
|
id, err := parseInviteId(r)
|
|
if err != nil {
|
|
http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := h.CancelInviteUseCase.CancelInvite(r.Context(), id); err != nil {
|
|
writeManageInviteError(w, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *InviteHandler) RenewInvite(w http.ResponseWriter, r *http.Request) {
|
|
id, err := parseInviteId(r)
|
|
if err != nil {
|
|
http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
userId, ok := claims.UserId(r)
|
|
if !ok {
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
renewed, err := h.RenewInviteUseCase.RenewInvite(r.Context(), useCases.RenewInviteCommand{ID: id, RenewedBy: userId})
|
|
if err != nil {
|
|
writeManageInviteError(w, err)
|
|
return
|
|
}
|
|
writeInviteResponse(w, http.StatusOK, renewed)
|
|
}
|
|
|
|
func writeManageInviteError(w http.ResponseWriter, err error) {
|
|
switch {
|
|
case errors.Is(err, invite.ErrInviteNotFound):
|
|
http.Error(w, apperr.InviteNotFoundErrorMessage, http.StatusNotFound)
|
|
case errors.Is(err, invite.ErrInviteAlreadyAccepted):
|
|
http.Error(w, apperr.InviteAlreadyAcceptedErrorMessage, http.StatusConflict)
|
|
case errors.Is(err, invite.ErrInviteEmailNotSent):
|
|
http.Error(w, apperr.InviteNotSentErrorMessage, http.StatusInternalServerError)
|
|
case errors.Is(err, user.ErrUserNotFound):
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
default:
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func writeCreateInviteError(w http.ResponseWriter, err error) {
|
|
switch {
|
|
case errors.Is(err, invite.ErrEmailHasAccount):
|
|
http.Error(w, apperr.EmailHasAccountErrorMessage, http.StatusConflict)
|
|
case errors.Is(err, invite.ErrPendingInvite):
|
|
http.Error(w, apperr.PendingInviteErrorMessage, http.StatusConflict)
|
|
case errors.Is(err, invite.ErrInviteEmailNotSent):
|
|
http.Error(w, apperr.InviteNotSentErrorMessage, http.StatusInternalServerError)
|
|
case errors.Is(err, user.ErrUserNotFound):
|
|
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
|
|
default:
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (h *InviteHandler) ListUsers(w http.ResponseWriter, _ *http.Request) {
|
|
entries := h.ListUsersAndInvitesUseCase.ListUsersAndInvites()
|
|
|
|
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.Status, entry.CreatedAt})
|
|
continue
|
|
}
|
|
items = append(items, userListItem{entry.ID, entry.Kind, entry.Username, entry.DisplayName, entry.Email, entry.Role, entry.Status, entry.CreatedAt})
|
|
}
|
|
|
|
body, err := json.Marshal(items)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
_, _ = w.Write(body)
|
|
}
|