F1b: 6 tabelas anon-facing ficam em public (decisao roteamento anon)

Fluxos anon identificam tenant por token/slug e nao resolvem o schema fisico.
Decisao (opcao C): manter em public com RLS por token. Volta a global:
patient_intake_requests, patient_invites, patient_invite_attempts,
document_share_links, agendador_configuracoes, agendador_solicitacoes.

- migration 20260613000001_f1b: remove as 6 do _tenant_template (template v2,
  78 tabelas). Smoke: clone gera 78, zero tabelas anon no schema, drop limpo
- frontend: 38 cadeias em 14 arquivos revertidas tenantDb().from() ->
  supabase.from() com tenant_id/owner_id restaurado (via comparacao com main)
- edge: convert-abandoned-intakes restaurada do main (SELECT global)
- save-intake-progress: ja usava public, sem mudanca
- doc F0 atualizado: 78 tenant + 59 global

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-06-13 09:09:46 -03:00
parent 9b21642e15
commit f17e9ee786
17 changed files with 164 additions and 98 deletions
@@ -79,7 +79,7 @@ async function onFileSelected(event, field) {
// Persiste imediatamente no banco sem fechar o accordion
const uid = ownerId.value;
const tenantId = await getActiveTenantId(uid);
await tenantDb().from('agendador_configuracoes').upsert({ owner_id: uid, ...buildPayload('identidade'), updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
await supabase.from('agendador_configuracoes').upsert({ owner_id: uid, tenant_id: tenantId, ...buildPayload('identidade'), updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
toast.add({ severity: 'success', summary: 'Imagem salva', life: 2000 });
}
} catch (e) {
@@ -361,7 +361,7 @@ async function load() {
const uid = await getOwnerId();
ownerId.value = uid;
const [{ data, error }] = await Promise.all([tenantDb().from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(), loadPaymentSettings(uid)]);
const [{ data, error }] = await Promise.all([supabase.from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(), loadPaymentSettings(uid)]);
if (error) throw error;
@@ -400,7 +400,7 @@ async function toggleAtivo() {
try {
const tenantId = await getActiveTenantId(uid);
await tenantDb().from('agendador_configuracoes').upsert({ owner_id: uid, ativo: novoAtivo, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
await supabase.from('agendador_configuracoes').upsert({ owner_id: uid, tenant_id: tenantId, ativo: novoAtivo, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
toast.add({
severity: novoAtivo ? 'success' : 'info',
@@ -422,7 +422,7 @@ async function saveCard(cardKey) {
const tenantId = await getActiveTenantId(uid);
const payload = buildPayload(cardKey);
await tenantDb().from('agendador_configuracoes').upsert({ owner_id: uid, ...payload, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
await supabase.from('agendador_configuracoes').upsert({ owner_id: uid, tenant_id: tenantId, ...payload, updated_at: new Date().toISOString() }, { onConflict: 'owner_id' });
toast.add({ severity: 'success', summary: 'Salvo', life: 2500 });
expandedCard.value = new Set();
+10 -7
View File
@@ -343,7 +343,7 @@ async function load() {
ownerId.value = uid;
const [{ data, error }] = await Promise.all([
tenantDb().from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(),
supabase.from('agendador_configuracoes').select('*').eq('owner_id', uid).maybeSingle(),
loadPaymentSettings(uid)
]);
if (error) throw error;
@@ -382,9 +382,10 @@ async function toggleAtivo() {
cfg.value.ativo = novoAtivo;
try {
const tenantId = await getActiveTenantId(uid);
const { error } = await tenantDb().from('agendador_configuracoes')
const { error } = await supabase
.from('agendador_configuracoes')
.upsert(
{ owner_id: uid, ativo: novoAtivo, updated_at: new Date().toISOString() },
{ owner_id: uid, tenant_id: tenantId, ativo: novoAtivo, updated_at: new Date().toISOString() },
{ onConflict: 'owner_id' }
);
if (error) throw error;
@@ -473,9 +474,10 @@ async function saveCard(cardKey) {
const uid = ownerId.value;
const tenantId = await getActiveTenantId(uid);
const payload = buildPayload(cardKey);
const { error } = await tenantDb().from('agendador_configuracoes')
const { error } = await supabase
.from('agendador_configuracoes')
.upsert(
{ owner_id: uid, ...payload, updated_at: new Date().toISOString() },
{ owner_id: uid, tenant_id: tenantId, ...payload, updated_at: new Date().toISOString() },
{ onConflict: 'owner_id' }
);
if (error) throw error;
@@ -516,9 +518,10 @@ async function onFileSelected(event, field) {
if (field === 'fundo') cfg.value.imagem_fundo_url = url;
const uid = ownerId.value;
const tenantId = await getActiveTenantId(uid);
await tenantDb().from('agendador_configuracoes')
await supabase
.from('agendador_configuracoes')
.upsert(
{ owner_id: uid, ...buildPayload('identidade'), updated_at: new Date().toISOString() },
{ owner_id: uid, tenant_id: tenantId, ...buildPayload('identidade'), updated_at: new Date().toISOString() },
{ onConflict: 'owner_id' }
);
toast.add({ severity: 'success', summary: 'Imagem salva', life: 2000 });
@@ -222,12 +222,14 @@ async function fetchSolicitacoes() {
if (!ownerId.value) return;
loading.value = true;
try {
let q = tenantDb().from('agendador_solicitacoes')
.select('id, owner_id, paciente_nome, paciente_sobrenome, paciente_email, paciente_celular, paciente_cpf, tipo, modalidade, data_solicitada, hora_solicitada, reservado_ate, motivo, como_conheceu, status, recusado_motivo, autorizado_em, created_at')
let q = supabase
.from('agendador_solicitacoes')
.select('id, owner_id, tenant_id, paciente_nome, paciente_sobrenome, paciente_email, paciente_celular, paciente_cpf, tipo, modalidade, data_solicitada, hora_solicitada, reservado_ate, motivo, como_conheceu, status, recusado_motivo, autorizado_em, created_at')
.order('data_solicitada', { ascending: false })
.order('hora_solicitada', { ascending: true });
if (!isClinic.value) q = q.eq('owner_id', ownerId.value);
if (isClinic.value) q = q.eq('tenant_id', tenantId.value);
else q = q.eq('owner_id', ownerId.value);
const { data, error } = await q;
if (error) throw error;
@@ -293,7 +295,8 @@ async function autorizar() {
if (!item) return;
dlg.value.saving = true;
try {
const { error } = await tenantDb().from('agendador_solicitacoes')
const { error } = await supabase
.from('agendador_solicitacoes')
.update({ status: 'autorizado', autorizado_em: new Date().toISOString() })
.eq('id', item.id);
if (error) throw error;
@@ -323,7 +326,8 @@ async function recusar() {
dlg.value.saving = true;
try {
const motivo = String(dlg.value.recusa_note || '').trim() || null;
const { error } = await tenantDb().from('agendador_solicitacoes')
const { error } = await supabase
.from('agendador_solicitacoes')
.update({ status: 'recusado', recusado_motivo: motivo })
.eq('id', item.id);
if (error) throw error;
@@ -442,7 +446,8 @@ async function onEventSaved(arg) {
const dbPayload = {};
for (const k of dbFields) if (normalized[k] !== undefined) dbPayload[k] = normalized[k];
await createEvento(dbPayload);
const { error } = await tenantDb().from('agendador_solicitacoes')
const { error } = await supabase
.from('agendador_solicitacoes')
.update({ status: 'convertido' })
.eq('id', target.id);
if (error) throw error;
@@ -16,7 +16,6 @@ import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
import { useToast } from 'primevue/usetoast';
import { useConfirm } from 'primevue/useconfirm';
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import { useTenantStore } from '@/stores/tenantStore';
// Fase 2 (Graphify hotspot): convertToPatient duplicado em 2 pages — extração pro repository.
import { createPatient, markIntakeConverted } from '@/features/patients/services/patientsRepository';
@@ -258,7 +257,8 @@ const pagedItems = computed(() => {
async function fetchIntakes() {
loading.value = true;
try {
const { data, error } = await tenantDb().from('patient_intake_requests').select('*')
const { data, error } = await supabase
.from('patient_intake_requests').select('*')
.order('created_at', { ascending: false });
if (error) throw error;
const weight = (s) => (s === 'new' ? 0 : s === 'converted' ? 1 : s === 'rejected' ? 2 : 9);
@@ -346,7 +346,7 @@ async function markRejected() {
dlg.value.saving = true;
try {
const reason = String(dlg.value.reject_note || '').trim() || null;
const { error } = await tenantDb().from('patient_intake_requests')
const { error } = await supabase.from('patient_intake_requests')
.update({ status: 'rejected', rejected_reason: reason, updated_at: new Date().toISOString() })
.eq('id', item.id);
if (error) throw error;