fix: liberar requireDeveloper para admin/dev, mapeamento de organizacoes e schema system_settings

This commit is contained in:
2026-08-28 10:49:16 +00:00
parent e2a3e3973f
commit 018be51ad5
4 changed files with 120 additions and 51 deletions
+16 -14
View File
@@ -346,11 +346,12 @@ export const DeveloperDashboard: React.FC = () => {
) : (
<div className="space-y-6">
{globalOrgs.map((org) => {
const admins = org.members.filter(m => m.role === 'admin');
const commonUsers = org.members.filter(m => m.role !== 'admin');
const membersList = org.members || [];
const admins = membersList.filter(m => m.role === 'admin');
const commonUsers = membersList.filter(m => m.role !== 'admin');
return (
<div key={org._id} className={clsx(
<div key={org._id || org.id} className={clsx(
"bg-surface-soft border rounded-xl overflow-hidden transition-all",
org.isBanned
? "border-red-500/50 shadow-[0_0_15px_rgba(239,68,68,0.1)]"
@@ -372,20 +373,21 @@ export const DeveloperDashboard: React.FC = () => {
)}
</div>
<div className="font-bold text-text-main text-base mb-0.5">{org.name || 'Organização Sem Nome'}</div>
<div className="font-mono text-indigo-400/80 text-xs select-all" title="ID da Organização">{org._id}</div>
<div className="font-mono text-indigo-400/80 text-xs select-all" title="ID da Organização">{org._id || org.id}</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<div className="text-xs font-bold text-text-muted uppercase tracking-wider mb-1">Total de Membros</div>
<span className="bg-surface-hover px-2 py-1 rounded text-sm font-bold">{org.memberCount}</span>
<span className="bg-surface-hover px-2 py-1 rounded text-sm font-bold">{org.memberCount || membersList.length}</span>
</div>
<button
onClick={async () => {
const orgTargetId = org._id || org.id || '';
if (!orgTargetId) return;
if (confirm(org.isBanned ? 'Deseja desbloquear esta organização?' : 'Deseja BLOQUEAR esta organização? Os usuários perderão o acesso imediatamente.')) {
try {
await systemSettingsService.toggleOrganizationBan(org._id, !org.isBanned);
await systemSettingsService.toggleOrganizationBan(orgTargetId, !org.isBanned);
showToast(`Organização ${org.isBanned ? 'desbloqueada' : 'bloqueada'} com sucesso!`, 'success');
// Refresh list
const data = await systemSettingsService.getGlobalOrganizations();
setGlobalOrgs(data);
} catch (e) {
@@ -403,7 +405,7 @@ export const DeveloperDashboard: React.FC = () => {
title={org.isBanned ? "Desbloquear Organização" : "Bloquear Organização"}
>
{org.isBanned ? (
<Users size={18} /> // Unblock icon
<Users size={18} />
) : (
<X size={18} />
)}
@@ -419,12 +421,12 @@ export const DeveloperDashboard: React.FC = () => {
</h3>
<div className="space-y-2">
{admins.map(admin => (
<div key={admin.userId} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-indigo-500/20">
<div key={admin.userId || admin.email} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-indigo-500/20">
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center text-indigo-500 font-bold text-xs">
{admin.name.charAt(0).toUpperCase()}
{(admin.name || 'A').charAt(0).toUpperCase()}
</div>
<div className="overflow-hidden">
<div className="font-bold text-sm truncate text-text-main">{admin.name}</div>
<div className="font-bold text-sm truncate text-text-main">{admin.name || 'Administrador'}</div>
<div className="text-[10px] text-text-muted truncate">{admin.email}</div>
</div>
{admin.isBanned && <span className="ml-auto text-error text-[10px] uppercase font-bold">Banido</span>}
@@ -441,15 +443,15 @@ export const DeveloperDashboard: React.FC = () => {
</h3>
<div className="space-y-2">
{commonUsers.map(user => (
<div key={user.userId} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-border/40">
<div key={user.userId || user.email} className="flex items-center gap-3 p-2 bg-surface rounded-lg border border-border/40">
<div className={clsx(
"w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs",
user.role === 'user' ? "bg-green-500/10 text-green-500" : "bg-gray-500/10 text-gray-500"
)}>
{user.name.charAt(0).toUpperCase()}
{(user.name || 'U').charAt(0).toUpperCase()}
</div>
<div className="overflow-hidden">
<div className="font-bold text-sm truncate text-text-main">{user.name}</div>
<div className="font-bold text-sm truncate text-text-main">{user.name || 'Usuário'}</div>
<div className="text-[10px] text-text-muted truncate">{user.email}</div>
</div>
<span className={clsx(
+2 -1
View File
@@ -61,8 +61,9 @@ export interface GlobalUser {
export interface GlobalOrganization {
_id: string; // organizationId
id?: string;
memberCount: number;
lastActive: string;
lastActive?: string;
isBanned: boolean;
name?: string; // Added
members: {
@@ -15,15 +15,44 @@ export const getSettings = async (req: Request, res: Response) => {
const { data: settings, error } = await supabase
.from('system_settings')
.select('*')
.eq('settings_id', 'global')
.eq('key', 'global')
.single();
if (error && error.code !== 'PGRST116') {
console.log('System settings table not found, returning defaults');
return res.json(DEFAULT_SETTINGS);
if (settings && settings.value) {
const val = typeof settings.value === 'string' ? JSON.parse(settings.value) : settings.value;
return res.json({
settingsId: 'global',
appName: val.appName || DEFAULT_SETTINGS.appName,
appSubtitle: val.appSubtitle || DEFAULT_SETTINGS.appSubtitle,
appLogoUrl: val.appLogoUrl || ''
});
}
res.json(settings || DEFAULT_SETTINGS);
const { data: fallback } = await supabase
.from('system_settings')
.select('*')
.limit(1)
.single();
if (fallback) {
if (fallback.value) {
const val = typeof fallback.value === 'string' ? JSON.parse(fallback.value) : fallback.value;
return res.json({
settingsId: 'global',
appName: val.appName || DEFAULT_SETTINGS.appName,
appSubtitle: val.appSubtitle || DEFAULT_SETTINGS.appSubtitle,
appLogoUrl: val.appLogoUrl || ''
});
}
return res.json({
settingsId: 'global',
appName: fallback.app_name || DEFAULT_SETTINGS.appName,
appSubtitle: fallback.app_subtitle || DEFAULT_SETTINGS.appSubtitle,
appLogoUrl: fallback.app_logo_url || ''
});
}
res.json(DEFAULT_SETTINGS);
} catch (error) {
console.error('Error fetching system settings:', error);
res.json(DEFAULT_SETTINGS);
@@ -33,48 +62,51 @@ export const getSettings = async (req: Request, res: Response) => {
export const updateSettings = async (req: Request, res: Response) => {
try {
const { appName, appSubtitle, appLogoUrl } = req.body;
const orgId = req.appUser?.organizationId || 'e47e6210-4879-4e5b-bf21-9285d2713123';
const { data: existing, error: fetchError } = await supabase
const { data: existing } = await supabase
.from('system_settings')
.select('*')
.eq('settings_id', 'global')
.eq('key', 'global')
.single();
let settings;
if (fetchError || !existing) {
const { data, error } = await supabase
const valPayload = {
appName: appName || DEFAULT_SETTINGS.appName,
appSubtitle: appSubtitle || DEFAULT_SETTINGS.appSubtitle,
appLogoUrl: appLogoUrl || '',
updatedBy: req.appUser?.email || 'admin',
updatedAt: new Date().toISOString()
};
if (!existing) {
const { error } = await supabase
.from('system_settings')
.insert({
settings_id: 'global',
app_name: appName,
app_subtitle: appSubtitle,
app_logo_url: appLogoUrl,
updated_by: req.appUser?.email
})
.select()
.single();
organization_id: orgId,
key: 'global',
value: valPayload
});
if (error) throw error;
settings = data;
} else {
const { data, error } = await supabase
const { error } = await supabase
.from('system_settings')
.update({
app_name: appName,
app_subtitle: appSubtitle,
app_logo_url: appLogoUrl,
updated_by: req.appUser?.email
value: valPayload,
updated_at: new Date().toISOString()
})
.eq('id', existing.id)
.select()
.single();
.eq('id', existing.id);
if (error) throw error;
settings = data;
}
console.log(`⚙️ System Settings updated by ${req.appUser?.email}`);
res.json(settings);
res.json({
settingsId: 'global',
appName,
appSubtitle,
appLogoUrl
});
} catch (error) {
console.error('Error updating system settings:', error);
res.status(500).json({ error: 'Erro ao atualizar configurações do sistema' });
@@ -123,7 +155,19 @@ export const getGlobalUsers = async (req: Request, res: Response) => {
.order('created_at', { ascending: false });
if (error && error.code !== '42P01') throw error;
res.json(users || []);
const mappedUsers = (users || []).map((u: any) => ({
...u,
_id: u.id,
id: u.id,
name: u.name || u.email?.split('@')[0] || 'Usuário',
email: u.email || '',
role: u.role || 'user',
isBanned: u.is_banned ?? false,
createdAt: u.created_at
}));
res.json(mappedUsers);
} catch (error) {
console.error('Error getting global users:', error);
res.status(500).json({ error: 'Erro ao buscar usuários globais.' });
@@ -144,14 +188,29 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
const orgsWithMembers = await Promise.all(
organizations.map(async (org) => {
const { data: members } = await supabase
.from('user_organizations')
.select('*')
.eq('organization_id', org.id);
let members: any[] = [];
try {
const { data: mems } = await supabase
.from('user_organizations')
.select('*')
.eq('organization_id', org.id);
members = mems || [];
} catch {
members = [];
}
return {
...org,
members: members || [],
_id: org.id,
id: org.id,
isBanned: org.is_banned ?? false,
members: members.map((m: any) => ({
userId: m.user_id || m.id,
name: m.name || m.user_name || 'Membro',
email: m.email || m.user_email || '',
role: m.role || 'user',
isBanned: m.is_banned ?? false
})),
memberCount: members?.length || 0
};
})
+8 -1
View File
@@ -73,7 +73,14 @@ export const canEdit = (req: Request, res: Response, next: NextFunction) => {
};
export const requireDeveloper = (req: Request, res: Response, next: NextFunction) => {
if (!req.appUser || req.appUser.email !== 'admtracksteel@gmail.com') {
const isDev = req.appUser && (
req.appUser.email === 'admtracksteel@gmail.com' ||
req.appUser.role === 'admin' ||
req.appUser.id === 'admin-user' ||
req.appUser.id === 'c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a10' ||
req.appUser.logtoId === 'admin'
);
if (!isDev) {
return res.status(403).json({ error: 'Acesso restrito ao desenvolvedor.' });
}
next();