feat: redesign superadmin agencies list, implement flat design, add date filters, and fix UI bugs
This commit is contained in:
168
backend/internal/repository/agency_template_repository.go
Normal file
168
backend/internal/repository/agency_template_repository.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"aggios-app/backend/internal/domain"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type AgencyTemplateRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewAgencyTemplateRepository(db *sql.DB) *AgencyTemplateRepository {
|
||||
return &AgencyTemplateRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *AgencyTemplateRepository) Create(template *domain.AgencySignupTemplate) error {
|
||||
query := `
|
||||
INSERT INTO agency_signup_templates (
|
||||
name, slug, description, form_fields, available_modules,
|
||||
custom_primary_color, custom_logo_url, redirect_url, success_message, max_uses
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
return r.db.QueryRow(
|
||||
query,
|
||||
template.Name,
|
||||
template.Slug,
|
||||
template.Description,
|
||||
template.FormFields,
|
||||
template.AvailableModules,
|
||||
template.CustomPrimaryColor,
|
||||
template.CustomLogoURL,
|
||||
template.RedirectURL,
|
||||
template.SuccessMessage,
|
||||
template.MaxUses,
|
||||
).Scan(&template.ID, &template.CreatedAt, &template.UpdatedAt)
|
||||
}
|
||||
|
||||
func (r *AgencyTemplateRepository) FindBySlug(slug string) (*domain.AgencySignupTemplate, error) {
|
||||
var template domain.AgencySignupTemplate
|
||||
query := `
|
||||
SELECT id, name, slug, description, form_fields, available_modules,
|
||||
custom_primary_color, custom_logo_url, redirect_url, success_message,
|
||||
is_active, usage_count, max_uses, expires_at, created_at, updated_at
|
||||
FROM agency_signup_templates
|
||||
WHERE slug = $1 AND is_active = true
|
||||
`
|
||||
|
||||
err := r.db.QueryRow(query, slug).Scan(
|
||||
&template.ID, &template.Name, &template.Slug, &template.Description,
|
||||
&template.FormFields, &template.AvailableModules,
|
||||
&template.CustomPrimaryColor, &template.CustomLogoURL,
|
||||
&template.RedirectURL, &template.SuccessMessage,
|
||||
&template.IsActive, &template.UsageCount, &template.MaxUses,
|
||||
&template.ExpiresAt, &template.CreatedAt, &template.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validar se expirou
|
||||
if template.ExpiresAt.Valid && template.ExpiresAt.Time.Before(sql.NullTime{}.Time) {
|
||||
return nil, fmt.Errorf("template expired")
|
||||
}
|
||||
|
||||
// Validar limite de usos
|
||||
if template.MaxUses.Valid && template.UsageCount >= int(template.MaxUses.Int64) {
|
||||
return nil, fmt.Errorf("template usage limit reached")
|
||||
}
|
||||
|
||||
return &template, nil
|
||||
}
|
||||
|
||||
func (r *AgencyTemplateRepository) List() ([]domain.AgencySignupTemplate, error) {
|
||||
var templates []domain.AgencySignupTemplate
|
||||
query := `
|
||||
SELECT id, name, slug, description, form_fields, available_modules,
|
||||
custom_primary_color, custom_logo_url, redirect_url, success_message,
|
||||
is_active, usage_count, max_uses, expires_at, created_at, updated_at
|
||||
FROM agency_signup_templates
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var t domain.AgencySignupTemplate
|
||||
if err := rows.Scan(
|
||||
&t.ID, &t.Name, &t.Slug, &t.Description,
|
||||
&t.FormFields, &t.AvailableModules,
|
||||
&t.CustomPrimaryColor, &t.CustomLogoURL,
|
||||
&t.RedirectURL, &t.SuccessMessage,
|
||||
&t.IsActive, &t.UsageCount, &t.MaxUses,
|
||||
&t.ExpiresAt, &t.CreatedAt, &t.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
templates = append(templates, t)
|
||||
}
|
||||
|
||||
return templates, rows.Err()
|
||||
}
|
||||
|
||||
func (r *AgencyTemplateRepository) IncrementUsageCount(id string) error {
|
||||
query := `UPDATE agency_signup_templates SET usage_count = usage_count + 1 WHERE id = $1`
|
||||
_, err := r.db.Exec(query, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *AgencyTemplateRepository) Update(template *domain.AgencySignupTemplate) error {
|
||||
query := `
|
||||
UPDATE agency_signup_templates
|
||||
SET name = $1, description = $2, form_fields = $3, available_modules = $4,
|
||||
custom_primary_color = $5, custom_logo_url = $6, redirect_url = $7,
|
||||
success_message = $8, is_active = $9, max_uses = $10, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $11
|
||||
`
|
||||
|
||||
_, err := r.db.Exec(
|
||||
query,
|
||||
template.Name,
|
||||
template.Description,
|
||||
template.FormFields,
|
||||
template.AvailableModules,
|
||||
template.CustomPrimaryColor,
|
||||
template.CustomLogoURL,
|
||||
template.RedirectURL,
|
||||
template.SuccessMessage,
|
||||
template.IsActive,
|
||||
template.MaxUses,
|
||||
template.ID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *AgencyTemplateRepository) Delete(id string) error {
|
||||
query := `DELETE FROM agency_signup_templates WHERE id = $1`
|
||||
_, err := r.db.Exec(query, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Helper: Convert form fields to JSON
|
||||
func FormFieldsToJSON(fields []string) ([]byte, error) {
|
||||
type FormField struct {
|
||||
Name string `json:"name"`
|
||||
Required bool `json:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
var formFields []FormField
|
||||
for _, field := range fields {
|
||||
formFields = append(formFields, FormField{
|
||||
Name: field,
|
||||
Required: field == "agencyName" || field == "subdomain" || field == "adminEmail" || field == "adminPassword",
|
||||
Enabled: true,
|
||||
})
|
||||
}
|
||||
|
||||
return json.Marshal(formFields)
|
||||
}
|
||||
280
backend/internal/repository/signup_template_repository.go
Normal file
280
backend/internal/repository/signup_template_repository.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"aggios-app/backend/internal/domain"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type SignupTemplateRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewSignupTemplateRepository(db *sql.DB) *SignupTemplateRepository {
|
||||
return &SignupTemplateRepository{db: db}
|
||||
}
|
||||
|
||||
// Create cria um novo template de cadastro
|
||||
func (r *SignupTemplateRepository) Create(ctx context.Context, template *domain.SignupTemplate) error {
|
||||
formFieldsJSON, err := json.Marshal(template.FormFields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling form_fields: %w", err)
|
||||
}
|
||||
|
||||
modulesJSON, err := json.Marshal(template.EnabledModules)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling enabled_modules: %w", err)
|
||||
}
|
||||
|
||||
query := `
|
||||
INSERT INTO signup_templates (
|
||||
name, description, slug, form_fields, enabled_modules,
|
||||
redirect_url, success_message, custom_logo_url, custom_primary_color,
|
||||
is_active, created_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
err = r.db.QueryRowContext(
|
||||
ctx, query,
|
||||
template.Name, template.Description, template.Slug,
|
||||
formFieldsJSON, modulesJSON,
|
||||
template.RedirectURL, template.SuccessMessage,
|
||||
template.CustomLogoURL, template.CustomPrimaryColor,
|
||||
template.IsActive, template.CreatedBy,
|
||||
).Scan(&template.ID, &template.CreatedAt, &template.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating signup template: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindBySlug busca um template pelo slug
|
||||
func (r *SignupTemplateRepository) FindBySlug(ctx context.Context, slug string) (*domain.SignupTemplate, error) {
|
||||
query := `
|
||||
SELECT id, name, description, slug, form_fields, enabled_modules,
|
||||
redirect_url, success_message, custom_logo_url, custom_primary_color,
|
||||
is_active, usage_count, created_by, created_at, updated_at
|
||||
FROM signup_templates
|
||||
WHERE slug = $1 AND is_active = true
|
||||
`
|
||||
|
||||
var template domain.SignupTemplate
|
||||
var formFieldsJSON, modulesJSON []byte
|
||||
var redirectURL, successMessage, customLogoURL, customPrimaryColor sql.NullString
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, slug).Scan(
|
||||
&template.ID, &template.Name, &template.Description, &template.Slug,
|
||||
&formFieldsJSON, &modulesJSON,
|
||||
&redirectURL, &successMessage,
|
||||
&customLogoURL, &customPrimaryColor,
|
||||
&template.IsActive, &template.UsageCount, &template.CreatedBy,
|
||||
&template.CreatedAt, &template.UpdatedAt,
|
||||
)
|
||||
|
||||
if redirectURL.Valid {
|
||||
template.RedirectURL = redirectURL.String
|
||||
}
|
||||
if successMessage.Valid {
|
||||
template.SuccessMessage = successMessage.String
|
||||
}
|
||||
if customLogoURL.Valid {
|
||||
template.CustomLogoURL = customLogoURL.String
|
||||
}
|
||||
if customPrimaryColor.Valid {
|
||||
template.CustomPrimaryColor = customPrimaryColor.String
|
||||
}
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("signup template not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error finding signup template: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(formFieldsJSON, &template.FormFields); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling form_fields: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(modulesJSON, &template.EnabledModules); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling enabled_modules: %w", err)
|
||||
}
|
||||
|
||||
return &template, nil
|
||||
}
|
||||
|
||||
// FindByID busca um template pelo ID
|
||||
func (r *SignupTemplateRepository) FindByID(ctx context.Context, id uuid.UUID) (*domain.SignupTemplate, error) {
|
||||
query := `
|
||||
SELECT id, name, description, slug, form_fields, enabled_modules,
|
||||
redirect_url, success_message, custom_logo_url, custom_primary_color,
|
||||
is_active, usage_count, created_by, created_at, updated_at
|
||||
FROM signup_templates
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
var template domain.SignupTemplate
|
||||
var formFieldsJSON, modulesJSON []byte
|
||||
var redirectURL, successMessage, customLogoURL, customPrimaryColor sql.NullString
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||||
&template.ID, &template.Name, &template.Description, &template.Slug,
|
||||
&formFieldsJSON, &modulesJSON,
|
||||
&redirectURL, &successMessage,
|
||||
&customLogoURL, &customPrimaryColor,
|
||||
&template.IsActive, &template.UsageCount, &template.CreatedBy,
|
||||
&template.CreatedAt, &template.UpdatedAt,
|
||||
)
|
||||
|
||||
if redirectURL.Valid {
|
||||
template.RedirectURL = redirectURL.String
|
||||
}
|
||||
if successMessage.Valid {
|
||||
template.SuccessMessage = successMessage.String
|
||||
}
|
||||
if customLogoURL.Valid {
|
||||
template.CustomLogoURL = customLogoURL.String
|
||||
}
|
||||
if customPrimaryColor.Valid {
|
||||
template.CustomPrimaryColor = customPrimaryColor.String
|
||||
}
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("signup template not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error finding signup template: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(formFieldsJSON, &template.FormFields); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling form_fields: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(modulesJSON, &template.EnabledModules); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling enabled_modules: %w", err)
|
||||
}
|
||||
|
||||
return &template, nil
|
||||
}
|
||||
|
||||
// List lista todos os templates
|
||||
func (r *SignupTemplateRepository) List(ctx context.Context) ([]*domain.SignupTemplate, error) {
|
||||
query := `
|
||||
SELECT id, name, description, slug, form_fields, enabled_modules,
|
||||
redirect_url, success_message, custom_logo_url, custom_primary_color,
|
||||
is_active, usage_count, created_by, created_at, updated_at
|
||||
FROM signup_templates
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing signup templates: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var templates []*domain.SignupTemplate
|
||||
|
||||
for rows.Next() {
|
||||
var template domain.SignupTemplate
|
||||
var formFieldsJSON, modulesJSON []byte
|
||||
var redirectURL, successMessage, customLogoURL, customPrimaryColor sql.NullString
|
||||
|
||||
err := rows.Scan(
|
||||
&template.ID, &template.Name, &template.Description, &template.Slug,
|
||||
&formFieldsJSON, &modulesJSON,
|
||||
&redirectURL, &successMessage,
|
||||
&customLogoURL, &customPrimaryColor,
|
||||
&template.IsActive, &template.UsageCount, &template.CreatedBy,
|
||||
&template.CreatedAt, &template.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error scanning signup template: %w", err)
|
||||
}
|
||||
|
||||
if redirectURL.Valid {
|
||||
template.RedirectURL = redirectURL.String
|
||||
}
|
||||
if successMessage.Valid {
|
||||
template.SuccessMessage = successMessage.String
|
||||
}
|
||||
if customLogoURL.Valid {
|
||||
template.CustomLogoURL = customLogoURL.String
|
||||
}
|
||||
if customPrimaryColor.Valid {
|
||||
template.CustomPrimaryColor = customPrimaryColor.String
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(formFieldsJSON, &template.FormFields); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling form_fields: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(modulesJSON, &template.EnabledModules); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling enabled_modules: %w", err)
|
||||
}
|
||||
|
||||
templates = append(templates, &template)
|
||||
}
|
||||
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
// IncrementUsageCount incrementa o contador de uso
|
||||
func (r *SignupTemplateRepository) IncrementUsageCount(ctx context.Context, id uuid.UUID) error {
|
||||
query := `UPDATE signup_templates SET usage_count = usage_count + 1 WHERE id = $1`
|
||||
_, err := r.db.ExecContext(ctx, query, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update atualiza um template
|
||||
func (r *SignupTemplateRepository) Update(ctx context.Context, template *domain.SignupTemplate) error {
|
||||
formFieldsJSON, err := json.Marshal(template.FormFields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling form_fields: %w", err)
|
||||
}
|
||||
|
||||
modulesJSON, err := json.Marshal(template.EnabledModules)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling enabled_modules: %w", err)
|
||||
}
|
||||
|
||||
query := `
|
||||
UPDATE signup_templates SET
|
||||
name = $1, description = $2, slug = $3, form_fields = $4, enabled_modules = $5,
|
||||
redirect_url = $6, success_message = $7, custom_logo_url = $8, custom_primary_color = $9,
|
||||
is_active = $10
|
||||
WHERE id = $11
|
||||
`
|
||||
|
||||
_, err = r.db.ExecContext(
|
||||
ctx, query,
|
||||
template.Name, template.Description, template.Slug,
|
||||
formFieldsJSON, modulesJSON,
|
||||
template.RedirectURL, template.SuccessMessage,
|
||||
template.CustomLogoURL, template.CustomPrimaryColor,
|
||||
template.IsActive, template.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating signup template: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deleta um template
|
||||
func (r *SignupTemplateRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
query := `DELETE FROM signup_templates WHERE id = $1`
|
||||
_, err := r.db.ExecContext(ctx, query, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting signup template: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -19,14 +19,21 @@ func NewTenantRepository(db *sql.DB) *TenantRepository {
|
||||
return &TenantRepository{db: db}
|
||||
}
|
||||
|
||||
// DB returns the underlying database connection
|
||||
func (r *TenantRepository) DB() *sql.DB {
|
||||
return r.db
|
||||
}
|
||||
|
||||
// Create creates a new tenant
|
||||
func (r *TenantRepository) Create(tenant *domain.Tenant) error {
|
||||
query := `
|
||||
INSERT INTO tenants (
|
||||
id, name, domain, subdomain, cnpj, razao_social, email, website,
|
||||
address, city, state, zip, description, industry, created_at, updated_at
|
||||
id, name, domain, subdomain, cnpj, razao_social, email, phone, website,
|
||||
address, neighborhood, number, complement, city, state, zip,
|
||||
description, industry, team_size, primary_color, secondary_color,
|
||||
logo_url, logo_horizontal_url, created_at, updated_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
@@ -44,13 +51,22 @@ func (r *TenantRepository) Create(tenant *domain.Tenant) error {
|
||||
tenant.CNPJ,
|
||||
tenant.RazaoSocial,
|
||||
tenant.Email,
|
||||
tenant.Phone,
|
||||
tenant.Website,
|
||||
tenant.Address,
|
||||
tenant.Neighborhood,
|
||||
tenant.Number,
|
||||
tenant.Complement,
|
||||
tenant.City,
|
||||
tenant.State,
|
||||
tenant.Zip,
|
||||
tenant.Description,
|
||||
tenant.Industry,
|
||||
tenant.TeamSize,
|
||||
tenant.PrimaryColor,
|
||||
tenant.SecondaryColor,
|
||||
tenant.LogoURL,
|
||||
tenant.LogoHorizontalURL,
|
||||
tenant.CreatedAt,
|
||||
tenant.UpdatedAt,
|
||||
).Scan(&tenant.ID, &tenant.CreatedAt, &tenant.UpdatedAt)
|
||||
@@ -59,14 +75,16 @@ func (r *TenantRepository) Create(tenant *domain.Tenant) error {
|
||||
// FindByID finds a tenant by ID
|
||||
func (r *TenantRepository) FindByID(id uuid.UUID) (*domain.Tenant, error) {
|
||||
query := `
|
||||
SELECT id, name, domain, subdomain, cnpj, razao_social, email, phone, website,
|
||||
address, city, state, zip, description, industry, is_active, created_at, updated_at
|
||||
SELECT id, name, domain, subdomain, cnpj, razao_social, email, phone, website,
|
||||
address, neighborhood, number, complement, city, state, zip, description, industry, team_size,
|
||||
primary_color, secondary_color, logo_url, logo_horizontal_url,
|
||||
is_active, created_at, updated_at
|
||||
FROM tenants
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
tenant := &domain.Tenant{}
|
||||
var cnpj, razaoSocial, email, phone, website, address, city, state, zip, description, industry sql.NullString
|
||||
var cnpj, razaoSocial, email, phone, website, address, neighborhood, number, complement, city, state, zip, description, industry, teamSize, primaryColor, secondaryColor, logoURL, logoHorizontalURL sql.NullString
|
||||
|
||||
err := r.db.QueryRow(query, id).Scan(
|
||||
&tenant.ID,
|
||||
@@ -79,11 +97,19 @@ func (r *TenantRepository) FindByID(id uuid.UUID) (*domain.Tenant, error) {
|
||||
&phone,
|
||||
&website,
|
||||
&address,
|
||||
&neighborhood,
|
||||
&number,
|
||||
&complement,
|
||||
&city,
|
||||
&state,
|
||||
&zip,
|
||||
&description,
|
||||
&industry,
|
||||
&teamSize,
|
||||
&primaryColor,
|
||||
&secondaryColor,
|
||||
&logoURL,
|
||||
&logoHorizontalURL,
|
||||
&tenant.IsActive,
|
||||
&tenant.CreatedAt,
|
||||
&tenant.UpdatedAt,
|
||||
@@ -116,6 +142,15 @@ func (r *TenantRepository) FindByID(id uuid.UUID) (*domain.Tenant, error) {
|
||||
if address.Valid {
|
||||
tenant.Address = address.String
|
||||
}
|
||||
if neighborhood.Valid {
|
||||
tenant.Neighborhood = neighborhood.String
|
||||
}
|
||||
if number.Valid {
|
||||
tenant.Number = number.String
|
||||
}
|
||||
if complement.Valid {
|
||||
tenant.Complement = complement.String
|
||||
}
|
||||
if city.Valid {
|
||||
tenant.City = city.String
|
||||
}
|
||||
@@ -131,6 +166,21 @@ func (r *TenantRepository) FindByID(id uuid.UUID) (*domain.Tenant, error) {
|
||||
if industry.Valid {
|
||||
tenant.Industry = industry.String
|
||||
}
|
||||
if teamSize.Valid {
|
||||
tenant.TeamSize = teamSize.String
|
||||
}
|
||||
if primaryColor.Valid {
|
||||
tenant.PrimaryColor = primaryColor.String
|
||||
}
|
||||
if secondaryColor.Valid {
|
||||
tenant.SecondaryColor = secondaryColor.String
|
||||
}
|
||||
if logoURL.Valid {
|
||||
tenant.LogoURL = logoURL.String
|
||||
}
|
||||
if logoHorizontalURL.Valid {
|
||||
tenant.LogoHorizontalURL = logoHorizontalURL.String
|
||||
}
|
||||
|
||||
return tenant, nil
|
||||
}
|
||||
@@ -171,7 +221,7 @@ func (r *TenantRepository) SubdomainExists(subdomain string) (bool, error) {
|
||||
// FindAll returns all tenants
|
||||
func (r *TenantRepository) FindAll() ([]*domain.Tenant, error) {
|
||||
query := `
|
||||
SELECT id, name, domain, subdomain, is_active, created_at, updated_at
|
||||
SELECT id, name, domain, subdomain, email, phone, cnpj, logo_url, is_active, created_at, updated_at
|
||||
FROM tenants
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
@@ -185,11 +235,17 @@ func (r *TenantRepository) FindAll() ([]*domain.Tenant, error) {
|
||||
var tenants []*domain.Tenant
|
||||
for rows.Next() {
|
||||
tenant := &domain.Tenant{}
|
||||
var email, phone, cnpj, logoURL sql.NullString
|
||||
|
||||
err := rows.Scan(
|
||||
&tenant.ID,
|
||||
&tenant.Name,
|
||||
&tenant.Domain,
|
||||
&tenant.Subdomain,
|
||||
&email,
|
||||
&phone,
|
||||
&cnpj,
|
||||
&logoURL,
|
||||
&tenant.IsActive,
|
||||
&tenant.CreatedAt,
|
||||
&tenant.UpdatedAt,
|
||||
@@ -197,6 +253,20 @@ func (r *TenantRepository) FindAll() ([]*domain.Tenant, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if email.Valid {
|
||||
tenant.Email = email.String
|
||||
}
|
||||
if phone.Valid {
|
||||
tenant.Phone = phone.String
|
||||
}
|
||||
if cnpj.Valid {
|
||||
tenant.CNPJ = cnpj.String
|
||||
}
|
||||
if logoURL.Valid {
|
||||
tenant.LogoURL = logoURL.String
|
||||
}
|
||||
|
||||
tenants = append(tenants, tenant)
|
||||
}
|
||||
|
||||
@@ -209,7 +279,21 @@ func (r *TenantRepository) FindAll() ([]*domain.Tenant, error) {
|
||||
|
||||
// Delete removes a tenant (and cascades to related data)
|
||||
func (r *TenantRepository) Delete(id uuid.UUID) error {
|
||||
result, err := r.db.Exec(`DELETE FROM tenants WHERE id = $1`, id)
|
||||
// Start transaction
|
||||
tx, err := r.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Delete all users associated with this tenant first
|
||||
_, err = tx.Exec(`DELETE FROM users WHERE tenant_id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the tenant
|
||||
result, err := tx.Exec(`DELETE FROM tenants WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -223,7 +307,8 @@ func (r *TenantRepository) Delete(id uuid.UUID) error {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
return nil
|
||||
// Commit transaction
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// UpdateProfile updates tenant profile information
|
||||
@@ -237,13 +322,21 @@ func (r *TenantRepository) UpdateProfile(id uuid.UUID, updates map[string]interf
|
||||
phone = COALESCE($5, phone),
|
||||
website = COALESCE($6, website),
|
||||
address = COALESCE($7, address),
|
||||
city = COALESCE($8, city),
|
||||
state = COALESCE($9, state),
|
||||
zip = COALESCE($10, zip),
|
||||
description = COALESCE($11, description),
|
||||
industry = COALESCE($12, industry),
|
||||
updated_at = $13
|
||||
WHERE id = $14
|
||||
neighborhood = COALESCE($8, neighborhood),
|
||||
number = COALESCE($9, number),
|
||||
complement = COALESCE($10, complement),
|
||||
city = COALESCE($11, city),
|
||||
state = COALESCE($12, state),
|
||||
zip = COALESCE($13, zip),
|
||||
description = COALESCE($14, description),
|
||||
industry = COALESCE($15, industry),
|
||||
team_size = COALESCE($16, team_size),
|
||||
primary_color = COALESCE($17, primary_color),
|
||||
secondary_color = COALESCE($18, secondary_color),
|
||||
logo_url = COALESCE($19, logo_url),
|
||||
logo_horizontal_url = COALESCE($20, logo_horizontal_url),
|
||||
updated_at = $21
|
||||
WHERE id = $22
|
||||
`
|
||||
|
||||
_, err := r.db.Exec(
|
||||
@@ -255,14 +348,29 @@ func (r *TenantRepository) UpdateProfile(id uuid.UUID, updates map[string]interf
|
||||
updates["phone"],
|
||||
updates["website"],
|
||||
updates["address"],
|
||||
updates["neighborhood"],
|
||||
updates["number"],
|
||||
updates["complement"],
|
||||
updates["city"],
|
||||
updates["state"],
|
||||
updates["zip"],
|
||||
updates["description"],
|
||||
updates["industry"],
|
||||
updates["team_size"],
|
||||
updates["primary_color"],
|
||||
updates["secondary_color"],
|
||||
updates["logo_url"],
|
||||
updates["logo_horizontal_url"],
|
||||
time.Now(),
|
||||
id,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateStatus updates the is_active status of a tenant
|
||||
func (r *TenantRepository) UpdateStatus(id uuid.UUID, isActive bool) error {
|
||||
query := `UPDATE tenants SET is_active = $1, updated_at = $2 WHERE id = $3`
|
||||
_, err := r.db.Exec(query, isActive, time.Now(), id)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"aggios-app/backend/internal/domain"
|
||||
@@ -53,6 +54,8 @@ func (r *UserRepository) Create(user *domain.User) error {
|
||||
|
||||
// FindByEmail finds a user by email
|
||||
func (r *UserRepository) FindByEmail(email string) (*domain.User, error) {
|
||||
log.Printf("🔍 FindByEmail called with: %s", email)
|
||||
|
||||
query := `
|
||||
SELECT id, tenant_id, email, password_hash, first_name, role, created_at, updated_at
|
||||
FROM users
|
||||
@@ -72,10 +75,16 @@ func (r *UserRepository) FindByEmail(email string) (*domain.User, error) {
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
log.Printf("❌ User not found: %s", email)
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("❌ DB error finding user %s: %v", email, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, err
|
||||
log.Printf("✅ Found user: %s, role: %s", user.Email, user.Role)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// FindByID finds a user by ID
|
||||
|
||||
Reference in New Issue
Block a user