feat: integration v2 and API key PART 1/3

This commit is contained in:
fsylvain committed 2026-10-04 21:56:14 +02:00
1 parent 7e9bd9ba30
commit 844842fdae
40 files changed
+3782 -64

No files matched your search

+171
View File
@@ -0,0 +1,171 @@
package apikey
import (
"RenewCMS/api/pkg/apperr"
"RenewCMS/api/pkg/claims"
validator "RenewCMS/api/pkg/validator"
domain "RenewCMS/internal/domain/apikey"
"RenewCMS/internal/domain/user"
"RenewCMS/internal/infrastructure/useCases"
"encoding/json"
"errors"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
)
type Handler struct {
CreateUseCase *useCases.CreateAPIKeyUseCase
ListUseCase *useCases.ListAPIKeysUseCase
RevokeUseCase *useCases.RevokeAPIKeyUseCase
}
type CreateAPIKeyRequest struct {
Name string `json:"name" validate:"required,max=60"`
ExpiresAt *time.Time `json:"expires_at"`
}
type createdKeyResponse struct {
ID uint32 `json:"id"`
Name string `json:"name"`
Prefix string `json:"prefix"`
Key string `json:"key"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt *time.Time `json:"expires_at"`
}
type keyListItem struct {
ID uint32 `json:"id"`
UserID uint32 `json:"user_id"`
Name string `json:"name"`
Prefix string `json:"prefix"`
Status domain.Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at"`
ExpiresAt *time.Time `json:"expires_at"`
RevokedAt *time.Time `json:"revoked_at"`
}
func NewHandler(create *useCases.CreateAPIKeyUseCase, list *useCases.ListAPIKeysUseCase, revoke *useCases.RevokeAPIKeyUseCase) *Handler {
return &Handler{CreateUseCase: create, ListUseCase: list, RevokeUseCase: revoke}
}
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
var request CreateAPIKeyRequest
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.CreateUseCase.CreateAPIKey(useCases.CreateAPIKeyCommand{
UserID: userId,
Name: request.Name,
ExpiresAt: request.ExpiresAt,
})
if err != nil {
if errors.Is(err, domain.ErrExpiryNotInFuture) {
http.Error(w, apperr.APIKeyExpiryErrorMessage, http.StatusBadRequest)
return
}
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
body, err := json.Marshal(createdKeyResponse{
ID: created.Key.ID,
Name: created.Key.Name,
Prefix: created.Key.Prefix,
Key: created.RawKey,
CreatedAt: created.Key.CreatedAt,
ExpiresAt: created.Key.ExpiresAt,
})
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Cache-Control", "no-store")
w.WriteHeader(http.StatusCreated)
_, _ = w.Write(body)
}
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
userId, ok := claims.UserId(r)
if !ok {
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
return
}
entries, err := h.ListUseCase.ListAPIKeys(userId)
if err != nil {
if errors.Is(err, user.ErrUserNotFound) {
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
return
}
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
items := make([]keyListItem, 0, len(entries))
for _, entry := range entries {
key := entry.Key
items = append(items, keyListItem{
ID: key.ID,
UserID: key.UserID,
Name: key.Name,
Prefix: key.Prefix,
Status: entry.Status,
CreatedAt: key.CreatedAt,
LastUsedAt: key.LastUsedAt,
ExpiresAt: key.ExpiresAt,
RevokedAt: key.RevokedAt,
})
}
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)
}
func (h *Handler) Revoke(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
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
}
if err := h.RevokeUseCase.RevokeAPIKey(userId, uint32(id)); err != nil {
switch {
case errors.Is(err, domain.ErrAPIKeyNotFound):
http.Error(w, apperr.APIKeyNotFoundErrorMessage, http.StatusNotFound)
case errors.Is(err, user.ErrUserNotFound):
http.Error(w, apperr.UnauthorizedErrorMessage, http.StatusUnauthorized)
default:
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
return
}
w.WriteHeader(http.StatusNoContent)
}