Merge pull request #6 from rede5/codex/implementar-crud-completo-de-usuarios
Add user CRUD endpoints with seller scoping
This commit is contained in:
commit
b4fd89f4a8
6 changed files with 454 additions and 1 deletions
|
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/swaggo/http-swagger v1.3.4
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
github.com/swaggo/swag v1.16.6
|
github.com/swaggo/swag v1.16.6
|
||||||
|
golang.org/x/crypto v0.37.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -26,7 +27,6 @@ require (
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
|
||||||
golang.org/x/mod v0.21.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.13.0 // indirect
|
golang.org/x/sync v0.13.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,33 @@ type Company struct {
|
||||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User represents an authenticated actor inside a company.
|
||||||
|
type User struct {
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
CompanyID uuid.UUID `db:"company_id" json:"company_id"`
|
||||||
|
Role string `db:"role" json:"role"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Email string `db:"email" json:"email"`
|
||||||
|
PasswordHash string `db:"password_hash" json:"-"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserFilter captures listing constraints.
|
||||||
|
type UserFilter struct {
|
||||||
|
CompanyID *uuid.UUID
|
||||||
|
Limit int
|
||||||
|
Offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPage wraps paginated results.
|
||||||
|
type UserPage struct {
|
||||||
|
Users []User `json:"users"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
// Product represents a medicine SKU with batch tracking.
|
// Product represents a medicine SKU with batch tracking.
|
||||||
type Product struct {
|
type Product struct {
|
||||||
ID uuid.UUID `db:"id" json:"id"`
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -240,6 +241,265 @@ func (h *Handler) CreatePaymentPreference(w http.ResponseWriter, r *http.Request
|
||||||
writeJSON(w, http.StatusCreated, pref)
|
writeJSON(w, http.StatusCreated, pref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateUser handles the creation of platform users.
|
||||||
|
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requester, err := getRequester(r)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req createUserRequest
|
||||||
|
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(requester.Role, "Seller") {
|
||||||
|
if requester.CompanyID == nil {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("seller must include X-Company-ID header"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.CompanyID != *requester.CompanyID {
|
||||||
|
writeError(w, http.StatusForbidden, errors.New("seller can only manage their own company users"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &domain.User{
|
||||||
|
CompanyID: req.CompanyID,
|
||||||
|
Role: req.Role,
|
||||||
|
Name: req.Name,
|
||||||
|
Email: req.Email,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.svc.CreateUser(r.Context(), user, req.Password); err != nil {
|
||||||
|
writeError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusCreated, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUsers supports pagination and optional company filter.
|
||||||
|
func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requester, err := getRequester(r)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page, pageSize := parsePagination(r)
|
||||||
|
|
||||||
|
var companyFilter *uuid.UUID
|
||||||
|
if cid := r.URL.Query().Get("company_id"); cid != "" {
|
||||||
|
id, err := uuid.FromString(cid)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("invalid company_id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
companyFilter = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(requester.Role, "Seller") {
|
||||||
|
if requester.CompanyID == nil {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("seller must include X-Company-ID header"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
companyFilter = requester.CompanyID
|
||||||
|
}
|
||||||
|
|
||||||
|
pageResult, err := h.svc.ListUsers(r.Context(), domain.UserFilter{CompanyID: companyFilter}, page, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusOK, pageResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser returns a single user by ID.
|
||||||
|
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requester, err := getRequester(r)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := parseUUIDFromPath(r.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := h.svc.GetUser(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(requester.Role, "Seller") {
|
||||||
|
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||||
|
writeError(w, http.StatusForbidden, errors.New("seller can only view users from their company"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusOK, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser updates profile fields or password.
|
||||||
|
func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requester, err := getRequester(r)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := parseUUIDFromPath(r.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req updateUserRequest
|
||||||
|
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := h.svc.GetUser(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(requester.Role, "Seller") {
|
||||||
|
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||||
|
writeError(w, http.StatusForbidden, errors.New("seller can only update their company users"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.CompanyID != nil {
|
||||||
|
user.CompanyID = *req.CompanyID
|
||||||
|
}
|
||||||
|
if req.Role != nil {
|
||||||
|
user.Role = *req.Role
|
||||||
|
}
|
||||||
|
if req.Name != nil {
|
||||||
|
user.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Email != nil {
|
||||||
|
user.Email = *req.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
newPassword := ""
|
||||||
|
if req.Password != nil {
|
||||||
|
newPassword = *req.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.svc.UpdateUser(r.Context(), user, newPassword); err != nil {
|
||||||
|
writeError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusOK, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser removes a user by ID.
|
||||||
|
func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requester, err := getRequester(r)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := parseUUIDFromPath(r.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := h.svc.GetUser(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(requester.Role, "Seller") {
|
||||||
|
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||||
|
writeError(w, http.StatusForbidden, errors.New("seller can only delete their company users"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.svc.DeleteUser(r.Context(), id); err != nil {
|
||||||
|
writeError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
type createUserRequest struct {
|
||||||
|
CompanyID uuid.UUID `json:"company_id"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateUserRequest struct {
|
||||||
|
CompanyID *uuid.UUID `json:"company_id,omitempty"`
|
||||||
|
Role *string `json:"role,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
Password *string `json:"password,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type requester struct {
|
||||||
|
Role string
|
||||||
|
CompanyID *uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePagination(r *http.Request) (int, int) {
|
||||||
|
page := 1
|
||||||
|
pageSize := 20
|
||||||
|
|
||||||
|
if v := r.URL.Query().Get("page"); v != "" {
|
||||||
|
if p, err := strconv.Atoi(v); err == nil && p > 0 {
|
||||||
|
page = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := r.URL.Query().Get("page_size"); v != "" {
|
||||||
|
if ps, err := strconv.Atoi(v); err == nil && ps > 0 {
|
||||||
|
pageSize = ps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return page, pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRequester(r *http.Request) (requester, error) {
|
||||||
|
role := r.Header.Get("X-User-Role")
|
||||||
|
if role == "" {
|
||||||
|
role = "Admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
var companyID *uuid.UUID
|
||||||
|
if cid := r.Header.Get("X-Company-ID"); cid != "" {
|
||||||
|
id, err := uuid.FromString(cid)
|
||||||
|
if err != nil {
|
||||||
|
return requester{}, errors.New("invalid X-Company-ID header")
|
||||||
|
}
|
||||||
|
companyID = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
return requester{Role: role, CompanyID: companyID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type registerCompanyRequest struct {
|
type registerCompanyRequest struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
CNPJ string `json:"cnpj"`
|
CNPJ string `json:"cnpj"`
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
|
|
@ -127,6 +128,95 @@ func (r *Repository) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Repository) CreateUser(ctx context.Context, user *domain.User) error {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
user.CreatedAt = now
|
||||||
|
user.UpdatedAt = now
|
||||||
|
|
||||||
|
query := `INSERT INTO users (id, company_id, role, name, email, password_hash, created_at, updated_at)
|
||||||
|
VALUES (:id, :company_id, :role, :name, :email, :password_hash, :created_at, :updated_at)`
|
||||||
|
|
||||||
|
_, err := r.db.NamedExecContext(ctx, query, user)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) {
|
||||||
|
baseQuery := `FROM users`
|
||||||
|
var args []any
|
||||||
|
var clauses []string
|
||||||
|
|
||||||
|
if filter.CompanyID != nil {
|
||||||
|
clauses = append(clauses, fmt.Sprintf("company_id = $%d", len(args)+1))
|
||||||
|
args = append(args, *filter.CompanyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
where := ""
|
||||||
|
if len(clauses) > 0 {
|
||||||
|
where = " WHERE " + strings.Join(clauses, " AND ")
|
||||||
|
}
|
||||||
|
|
||||||
|
countQuery := "SELECT count(*) " + baseQuery + where
|
||||||
|
var total int64
|
||||||
|
if err := r.db.GetContext(ctx, &total, countQuery, args...); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, filter.Limit, filter.Offset)
|
||||||
|
listQuery := fmt.Sprintf("SELECT id, company_id, role, name, email, password_hash, created_at, updated_at %s%s ORDER BY created_at DESC LIMIT $%d OFFSET $%d", baseQuery, where, len(args)-1, len(args))
|
||||||
|
|
||||||
|
var users []domain.User
|
||||||
|
if err := r.db.SelectContext(ctx, &users, listQuery, args...); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) GetUser(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||||
|
var user domain.User
|
||||||
|
query := `SELECT id, company_id, role, name, email, password_hash, created_at, updated_at FROM users WHERE id = $1`
|
||||||
|
if err := r.db.GetContext(ctx, &user, query, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) UpdateUser(ctx context.Context, user *domain.User) error {
|
||||||
|
user.UpdatedAt = time.Now().UTC()
|
||||||
|
|
||||||
|
query := `UPDATE users
|
||||||
|
SET company_id = :company_id, role = :role, name = :name, email = :email, password_hash = :password_hash, updated_at = :updated_at
|
||||||
|
WHERE id = :id`
|
||||||
|
|
||||||
|
res, err := r.db.NamedExecContext(ctx, query, user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rows == 0 {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) DeleteUser(ctx context.Context, id uuid.UUID) error {
|
||||||
|
res, err := r.db.ExecContext(ctx, "DELETE FROM users WHERE id = $1", id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rows, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rows == 0 {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// InitSchema applies a minimal schema for development environments.
|
// InitSchema applies a minimal schema for development environments.
|
||||||
func (r *Repository) InitSchema(ctx context.Context) error {
|
func (r *Repository) InitSchema(ctx context.Context) error {
|
||||||
schema := `
|
schema := `
|
||||||
|
|
@ -140,6 +230,17 @@ CREATE TABLE IF NOT EXISTS companies (
|
||||||
updated_at TIMESTAMPTZ NOT NULL
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
company_id UUID NOT NULL REFERENCES companies(id),
|
||||||
|
role TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS products (
|
CREATE TABLE IF NOT EXISTS products (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
seller_id UUID NOT NULL REFERENCES companies(id),
|
seller_id UUID NOT NULL REFERENCES companies(id),
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,12 @@ func New(cfg config.Config) (*Server, error) {
|
||||||
mux.Handle("PATCH /api/orders/", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip))
|
mux.Handle("PATCH /api/orders/", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip))
|
||||||
mux.Handle("POST /api/orders/", chain(http.HandlerFunc(h.CreatePaymentPreference), middleware.Logger, middleware.Gzip))
|
mux.Handle("POST /api/orders/", chain(http.HandlerFunc(h.CreatePaymentPreference), middleware.Logger, middleware.Gzip))
|
||||||
|
|
||||||
|
mux.Handle("POST /api/v1/users", chain(http.HandlerFunc(h.CreateUser), middleware.Logger, middleware.Gzip))
|
||||||
|
mux.Handle("GET /api/v1/users", chain(http.HandlerFunc(h.ListUsers), middleware.Logger, middleware.Gzip))
|
||||||
|
mux.Handle("GET /api/v1/users/", chain(http.HandlerFunc(h.GetUser), middleware.Logger, middleware.Gzip))
|
||||||
|
mux.Handle("PUT /api/v1/users/", chain(http.HandlerFunc(h.UpdateUser), middleware.Logger, middleware.Gzip))
|
||||||
|
mux.Handle("DELETE /api/v1/users/", chain(http.HandlerFunc(h.DeleteUser), middleware.Logger, middleware.Gzip))
|
||||||
|
|
||||||
mux.Handle("GET /swagger/", httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json")))
|
mux.Handle("GET /swagger/", httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json")))
|
||||||
|
|
||||||
return &Server{cfg: cfg, db: db, mux: mux}, nil
|
return &Server{cfg: cfg, db: db, mux: mux}, nil
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package usecase
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
|
|
||||||
"github.com/saveinmed/backend-go/internal/domain"
|
"github.com/saveinmed/backend-go/internal/domain"
|
||||||
|
|
@ -19,6 +21,12 @@ type Repository interface {
|
||||||
CreateOrder(ctx context.Context, order *domain.Order) error
|
CreateOrder(ctx context.Context, order *domain.Order) error
|
||||||
GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error)
|
GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error)
|
||||||
UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error
|
UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error
|
||||||
|
|
||||||
|
CreateUser(ctx context.Context, user *domain.User) error
|
||||||
|
ListUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error)
|
||||||
|
GetUser(ctx context.Context, id uuid.UUID) (*domain.User, error)
|
||||||
|
UpdateUser(ctx context.Context, user *domain.User) error
|
||||||
|
DeleteUser(ctx context.Context, id uuid.UUID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PaymentGateway abstracts Mercado Pago integration.
|
// PaymentGateway abstracts Mercado Pago integration.
|
||||||
|
|
@ -75,3 +83,54 @@ func (s *Service) CreatePaymentPreference(ctx context.Context, id uuid.UUID) (*d
|
||||||
}
|
}
|
||||||
return s.pay.CreatePreference(ctx, order)
|
return s.pay.CreatePreference(ctx, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateUser(ctx context.Context, user *domain.User, password string) error {
|
||||||
|
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.ID = uuid.Must(uuid.NewV7())
|
||||||
|
user.PasswordHash = string(hashed)
|
||||||
|
|
||||||
|
return s.repo.CreateUser(ctx, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListUsers(ctx context.Context, filter domain.UserFilter, page, pageSize int) (*domain.UserPage, error) {
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.Limit = pageSize
|
||||||
|
filter.Offset = (page - 1) * pageSize
|
||||||
|
|
||||||
|
users, total, err := s.repo.ListUsers(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.UserPage{Users: users, Total: total, Page: page, PageSize: pageSize}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||||
|
return s.repo.GetUser(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateUser(ctx context.Context, user *domain.User, newPassword string) error {
|
||||||
|
if newPassword != "" {
|
||||||
|
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.PasswordHash = string(hashed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.UpdateUser(ctx, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteUser(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.repo.DeleteUser(ctx, id)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue