Arquitetura do Sistema
This page is a deep-dive into how FeedbackPulse SaaS is built. Understanding the architecture helps you debug issues, extend the platform, and make informed deployment decisions.
Stack Tecnologico
| Layer | Technology | Purpose |
|---|---|---|
| Backend | Laravel 12 (PHP 8.2+) | Application framework, routing, ORM, queue |
| Banco de Dados | MySQL 8.0+ / MariaDB 10.6+ | Persistent data storage |
| Frontend | Blade + Alpine.js + Tailwind CSS | Server-rendered UI with reactive components |
| Payments | Stripe PHP SDK + PayPal REST API | Subscription billing |
| AI | OpenAI GPT API | Sentiment analysis, auto-tagging, reply suggestions |
| Auth | Laravel Socialite | Google & GitHub OAuth |
| Laravel Mail (SMTP) | Transactional emails, digests, reports | |
| Real-Time | Server-Sent Events (SSE) | Live submission streaming |
| Task Scheduling | Laravel Scheduler (cron) | Digests, trial expiry, retencao de dados |
Modelo Multi-Inquilino
FeedbackPulse uses a single-database, shared-schema multi-tenancy model. This means:
- One database serves all tenants
- Every tenant-scoped table has a
tenant_idcolumn - A global scope (
TenantScope) automatically filters all queries to the current tenant - A trait (
BelongsToTenant) auto-fillstenant_idon creation and applies the scope
Como Funciona a Resolucao de Inquilinos
When a request comes in, the ResolveTenant middleware determines the current tenant using this priority:
- Subdomain —
acme.yourdomain.com→ looks up tenant with subdomain "acme" - Custom domain —
feedback.acmecorp.com→ looks up verified domain intenant_domainstable - Authenticated user — falls back to the logged-in user's
tenant_id
For public pages (like /wall/acme-corp), the tenant is resolved from the URL slug directly in the controller (bypassing middleware).
Isolamento de Dados
+------------------------------------------+
| Database |
| |
| products (tenant_id = 1) -> Acme data |
| products (tenant_id = 2) -> TechCorp |
| products (tenant_id = 3) -> E-Commerce |
| |
| TenantScope ensures Acme ONLY sees |
| rows where tenant_id = 1 |
+------------------------------------------+
Seguranca: Public-facing controllers use
withoutGlobalScopes()to bypass the tenant scope, then manually filter by tenant ID. This is intentional — public pages need to show data without an authenticated session.
Estrutura de Diretorios
feedbackpulse-saas/
+-- app/
| +-- Console/Commands/ # 7 artisan commands (digests, retention, alerts)
| +-- Http/
| | +-- Controllers/
| | | +-- Admin/ # Superadmin panel controllers
| | | +-- Auth/ # Login, register, 2FA, OAuth, impersonation
| | | +-- Customer/ # Customer portal
| | | +-- Public/ # Public pages (wall, form, roadmap, changelog, hub)
| | | +-- Tenant/ # Tenant dashboard controllers
| | | +-- Webhooks/ # Stripe & PayPal webhook handlers
| | +-- Middleware/ # 15 custom middleware
| +-- Mail/ # 7 mailable classes
| +-- Models/ # 28 Eloquent models
| +-- Scopes/ # TenantScope (global query scope)
| +-- Services/ # Business logic (AI, payments, webhooks, etc.)
| +-- Traits/ # BelongsToTenant trait
| +-- Providers/ # Service providers
+-- config/ # 11 Laravel config files
+-- database/
| +-- migrations/ # 35+ migration files
| +-- seeders/ # Demo data seeders
+-- public/ # Web root (index.php, assets, storage symlink)
+-- resources/views/ # 86 Blade templates
| +-- admin/ # Superadmin views
| +-- auth/ # Auth views (login, register, 2FA)
| +-- tenant/ # Tenant dashboard views
| +-- public/ # Public page views
| +-- emails/ # Email templates
| +-- layouts/ # Layout templates (admin, tenant, guest, install)
| +-- partials/ # Shared components (nav, meta, command palette)
| +-- install/ # Web installer views
| +-- legal/ # Privacy, terms, cookies pages
| +-- errors/ # Error pages (403, 404, 419, 429, 500)
| +-- landing/ # Landing page partials
+-- routes/
| +-- web.php # 416 lines of web routes
| +-- api.php # API v2 routes
+-- storage/ # Uploads, cache, sessions, logs
+-- bootstrap/ # Framework bootstrap files
Funcoes de Usuario
FeedbackPulse has four user roles, stored in the users.role column:
| Role | Access | Login URL |
|---|---|---|
superadmin | Full platform control (/admin/*) | /login |
tenant_admin | Full tenant control (/dashboard, /settings/*) | /login |
tenant_staff | Limited tenant access (no billing, no delete) | /login |
customer | Customer portal only (/customer/dashboard) | /customer/login |
Hierarquia de Funcoes
superadmin
+-- Can impersonate any tenant_admin
+-- tenant_admin
+-- Can invite tenant_staff
+-- Can manage billing, settings, team
+-- tenant_staff
+-- Can manage submissions, campaigns
+-- customer
+-- Can view their own feedback
Ciclo de Vida da Requisicao
Here's what happens when a request hits FeedbackPulse:
Browser Request
|
v
public/index.php
|
v
Laravel Kernel (middleware stack)
|
+-- EnsureInstalled -> Redirect to /install if not set up
+-- SecurityHeaders -> Add HSTS, CSP, X-Frame-Options
+-- VerifyCsrfToken -> Check CSRF token (except webhooks)
+-- ResolveTenant -> Determine current tenant
+-- Authenticate -> Check if user is logged in
+-- EnsureTenantAccess -> Verify user belongs to tenant
+-- EnsureTwoFactorVerified -> Check 2FA if enabled
+-- CheckPlanLimit -> Enforce plan feature limits
|
v
Controller (processes request)
|
v
Blade View (renders HTML)
|
v
Response -> Browser
Arquitetura do Banco de Dados
Tabelas Principais
| Table | Rows Per | Purpose |
|---|---|---|
tenants | Platform | Multi-tenant accounts |
users | Platform | All user accounts (all roles) |
plans | Platform | Subscription plans |
platform_settings | Platform | Global key-value config |
products | Tenant | Feedback products |
feedback_campaigns | Tenant | Feedback form configurations |
feedback_submissions | Tenant | Individual feedback entries |
feedback_tags | Tenant | Tags (many-to-many with submissions) |
roadmap_items | Tenant | Roadmap kanban items |
roadmap_votes | Tenant | Anonymous votes on roadmap items |
feature_requests | Tenant | Community feature suggestions |
changelog_entries | Tenant | Product release notes |
team_members | Tenant | Team member records |
team_invitations | Tenant | Pending invitations |
api_keys | Tenant | API access keys |
audit_logs | Platform | Action audit trail |
notifications | Platform | In-app notifications |
payment_events | Platform | Stripe/PayPal webhook events |
webhook_logs | Tenant | Outbound webhook delivery logs |
data_deletion_requests | Tenant | GDPR deletion requests |
landing_pages | Platform | Landing page builder data |
tenant_domains | Tenant | Custom domain mappings |
referral_codes | Tenant | Referral codes |
referral_conversions | Platform | Referral conversion tracking |
cron_logs | Platform | Scheduled task execution logs |
Indices-Chave
All tenant-scoped tables are indexed on (tenant_id, created_at) for optimal query performance. The feedback_submissions table has additional indexes on status, product_id, campaign_id, and sentiment_label.
Arquitetura de Seguranca
| Layer | Protection |
|---|---|
| Transport | HTTPS enforced, HSTS headers |
| Authentication | Bcrypt (12 rounds), optional 2FA (TOTP) |
| Authorization | Role-based middleware + policy classes |
| CSRF | Laravel CSRF tokens on all forms |
| XSS | Blade auto-escaping ({{ }}) |
| SQL Injection | Eloquent parameterized queries |
| Rate Limiting | Per-route throttling (5-120 req/min) |
| API Seguranca | SHA256-hashed Chaves API, per-tenant rate limits |
| Webhook Seguranca | HMAC signature verification (Stripe), SSRF protection |
| Data | Sensitive settings encrypted at rest |
| Sessions | Encrypted, HTTP-only, secure cookies |
| Headers | CSP, X-Frame-Options, X-Content-Type-Options |
Proximos Passos
- Glossary of Terms — understand the terminology
- Instalacao Guide — set up your server
- Banco de Dados Schema — detailed table structures