Inicial
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/techxcar/backend/pkg/database"
|
||||
)
|
||||
|
||||
var uuidRe = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
||||
|
||||
type SuperAdmin struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"-"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type Tenant struct {
|
||||
ID string `json:"id"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type TenantUser struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"-"`
|
||||
Role string `json:"role"`
|
||||
Name string `json:"name"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
type Invite struct {
|
||||
ID string `json:"id"`
|
||||
TenantID *string `json:"tenant_id"`
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
UsedAt *time.Time `json:"used_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
db *database.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *database.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
|
||||
func (r *Repository) GetSuperAdminByEmail(ctx context.Context, email string) (*SuperAdmin, error) {
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
`SELECT id, email, password_hash, created_at FROM super_admins WHERE email = $1`, email)
|
||||
var a SuperAdmin
|
||||
err := row.Scan(&a.ID, &a.Email, &a.PasswordHash, &a.CreatedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tenant: get super admin: %w", err)
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateSuperAdmin(ctx context.Context, email, passwordHash string) (*SuperAdmin, error) {
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
`INSERT INTO super_admins (email, password_hash) VALUES ($1, $2)
|
||||
RETURNING id, email, password_hash, created_at`,
|
||||
email, passwordHash)
|
||||
var a SuperAdmin
|
||||
if err := row.Scan(&a.ID, &a.Email, &a.PasswordHash, &a.CreatedAt); err != nil {
|
||||
return nil, fmt.Errorf("tenant: create super admin: %w", err)
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTenantBySlug(ctx context.Context, slug string) (*Tenant, error) {
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
`SELECT id, slug, name, status, created_at FROM tenants WHERE slug = $1`, slug)
|
||||
var t Tenant
|
||||
err := row.Scan(&t.ID, &t.Slug, &t.Name, &t.Status, &t.CreatedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tenant: get by slug: %w", err)
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTenantByID(ctx context.Context, id string) (*Tenant, error) {
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
`SELECT id, slug, name, status, created_at FROM tenants WHERE id = $1`, id)
|
||||
var t Tenant
|
||||
err := row.Scan(&t.ID, &t.Slug, &t.Name, &t.Status, &t.CreatedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tenant: get by id: %w", err)
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListTenants(ctx context.Context) ([]*Tenant, error) {
|
||||
rows, err := r.db.Pool.Query(ctx,
|
||||
`SELECT id, slug, name, status, created_at FROM tenants ORDER BY created_at DESC`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tenant: list: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var list []*Tenant
|
||||
for rows.Next() {
|
||||
var t Tenant
|
||||
if err := rows.Scan(&t.ID, &t.Slug, &t.Name, &t.Status, &t.CreatedAt); err != nil {
|
||||
return nil, fmt.Errorf("tenant: list scan: %w", err)
|
||||
}
|
||||
list = append(list, &t)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("tenant: list rows: %w", err)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateTenant(ctx context.Context, slug, name string) (*Tenant, error) {
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
`INSERT INTO tenants (slug, name) VALUES ($1, $2)
|
||||
RETURNING id, slug, name, status, created_at`,
|
||||
slug, name)
|
||||
var t Tenant
|
||||
if err := row.Scan(&t.ID, &t.Slug, &t.Name, &t.Status, &t.CreatedAt); err != nil {
|
||||
return nil, fmt.Errorf("tenant: create: %w", err)
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTenantUserByEmail(ctx context.Context, tenantID, email string) (*TenantUser, error) {
|
||||
if !uuidRe.MatchString(tenantID) {
|
||||
return nil, fmt.Errorf("tenant: invalid tenant ID format")
|
||||
}
|
||||
schema := `"tenant_` + strings.ReplaceAll(tenantID, "-", "_") + `"`
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
fmt.Sprintf(`SELECT id, email, password_hash, role, name, active FROM %s.users WHERE email = $1`, schema),
|
||||
email)
|
||||
var u TenantUser
|
||||
err := row.Scan(&u.ID, &u.Email, &u.PasswordHash, &u.Role, &u.Name, &u.Active)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tenant: get user: %w", err)
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateTenantUser(ctx context.Context, tenantID, email, passwordHash, name, role string) (*TenantUser, error) {
|
||||
if !uuidRe.MatchString(tenantID) {
|
||||
return nil, fmt.Errorf("tenant: invalid tenant ID format")
|
||||
}
|
||||
schema := `"tenant_` + strings.ReplaceAll(tenantID, "-", "_") + `"`
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
fmt.Sprintf(`INSERT INTO %s.users (email, password_hash, name, role)
|
||||
VALUES ($1, $2, $3, $4) RETURNING id, email, password_hash, role, name, active`, schema),
|
||||
email, passwordHash, name, role)
|
||||
var u TenantUser
|
||||
if err := row.Scan(&u.ID, &u.Email, &u.PasswordHash, &u.Role, &u.Name, &u.Active); err != nil {
|
||||
return nil, fmt.Errorf("tenant: create user: %w", err)
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateInvite(ctx context.Context, tenantID *string, expiresIn time.Duration) (*Invite, error) {
|
||||
token := uuid.New().String()
|
||||
expiresAt := time.Now().Add(expiresIn)
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
`INSERT INTO invites (tenant_id, token, expires_at) VALUES ($1, $2, $3)
|
||||
RETURNING id, tenant_id, token, expires_at, used_at, created_at`,
|
||||
tenantID, token, expiresAt)
|
||||
var inv Invite
|
||||
if err := row.Scan(&inv.ID, &inv.TenantID, &inv.Token, &inv.ExpiresAt, &inv.UsedAt, &inv.CreatedAt); err != nil {
|
||||
return nil, fmt.Errorf("tenant: create invite: %w", err)
|
||||
}
|
||||
return &inv, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetInviteByToken(ctx context.Context, token string) (*Invite, error) {
|
||||
row := r.db.Pool.QueryRow(ctx,
|
||||
`SELECT id, tenant_id, token, expires_at, used_at, created_at FROM invites WHERE token = $1`, token)
|
||||
var inv Invite
|
||||
err := row.Scan(&inv.ID, &inv.TenantID, &inv.Token, &inv.ExpiresAt, &inv.UsedAt, &inv.CreatedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tenant: get invite: %w", err)
|
||||
}
|
||||
return &inv, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UseInvite(ctx context.Context, inviteID string) error {
|
||||
tag, err := r.db.Pool.Exec(ctx,
|
||||
`UPDATE invites SET used_at = NOW() WHERE id = $1`, inviteID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tenant: use invite: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return fmt.Errorf("tenant: invite not found: %s", inviteID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user