fix: liberar requireDeveloper para admin/dev, mapeamento de organizacoes e schema system_settings
This commit is contained in:
@@ -346,11 +346,12 @@ export const DeveloperDashboard: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{globalOrgs.map((org) => {
|
{globalOrgs.map((org) => {
|
||||||
const admins = org.members.filter(m => m.role === 'admin');
|
const membersList = org.members || [];
|
||||||
const commonUsers = org.members.filter(m => m.role !== 'admin');
|
const admins = membersList.filter(m => m.role === 'admin');
|
||||||
|
const commonUsers = membersList.filter(m => m.role !== 'admin');
|
||||||
|
|
||||||
return (
|
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",
|
"bg-surface-soft border rounded-xl overflow-hidden transition-all",
|
||||||
org.isBanned
|
org.isBanned
|
||||||
? "border-red-500/50 shadow-[0_0_15px_rgba(239,68,68,0.1)]"
|
? "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>
|
||||||
<div className="font-bold text-text-main text-base mb-0.5">{org.name || 'Organização Sem Nome'}</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>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs font-bold text-text-muted uppercase tracking-wider mb-1">Total de Membros</div>
|
<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>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
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.')) {
|
if (confirm(org.isBanned ? 'Deseja desbloquear esta organização?' : 'Deseja BLOQUEAR esta organização? Os usuários perderão o acesso imediatamente.')) {
|
||||||
try {
|
try {
|
||||||
await systemSettingsService.toggleOrganizationBan(org._id, !org.isBanned);
|
await systemSettingsService.toggleOrganizationBan(orgTargetId, !org.isBanned);
|
||||||
showToast(`Organização ${org.isBanned ? 'desbloqueada' : 'bloqueada'} com sucesso!`, 'success');
|
showToast(`Organização ${org.isBanned ? 'desbloqueada' : 'bloqueada'} com sucesso!`, 'success');
|
||||||
// Refresh list
|
|
||||||
const data = await systemSettingsService.getGlobalOrganizations();
|
const data = await systemSettingsService.getGlobalOrganizations();
|
||||||
setGlobalOrgs(data);
|
setGlobalOrgs(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -403,7 +405,7 @@ export const DeveloperDashboard: React.FC = () => {
|
|||||||
title={org.isBanned ? "Desbloquear Organização" : "Bloquear Organização"}
|
title={org.isBanned ? "Desbloquear Organização" : "Bloquear Organização"}
|
||||||
>
|
>
|
||||||
{org.isBanned ? (
|
{org.isBanned ? (
|
||||||
<Users size={18} /> // Unblock icon
|
<Users size={18} />
|
||||||
) : (
|
) : (
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
)}
|
)}
|
||||||
@@ -419,12 +421,12 @@ export const DeveloperDashboard: React.FC = () => {
|
|||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{admins.map(admin => (
|
{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">
|
<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>
|
||||||
<div className="overflow-hidden">
|
<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 className="text-[10px] text-text-muted truncate">{admin.email}</div>
|
||||||
</div>
|
</div>
|
||||||
{admin.isBanned && <span className="ml-auto text-error text-[10px] uppercase font-bold">Banido</span>}
|
{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>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{commonUsers.map(user => (
|
{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(
|
<div className={clsx(
|
||||||
"w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs",
|
"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.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>
|
||||||
<div className="overflow-hidden">
|
<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 className="text-[10px] text-text-muted truncate">{user.email}</div>
|
||||||
</div>
|
</div>
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
|
|||||||
@@ -61,8 +61,9 @@ export interface GlobalUser {
|
|||||||
|
|
||||||
export interface GlobalOrganization {
|
export interface GlobalOrganization {
|
||||||
_id: string; // organizationId
|
_id: string; // organizationId
|
||||||
|
id?: string;
|
||||||
memberCount: number;
|
memberCount: number;
|
||||||
lastActive: string;
|
lastActive?: string;
|
||||||
isBanned: boolean;
|
isBanned: boolean;
|
||||||
name?: string; // Added
|
name?: string; // Added
|
||||||
members: {
|
members: {
|
||||||
|
|||||||
@@ -15,15 +15,44 @@ export const getSettings = async (req: Request, res: Response) => {
|
|||||||
const { data: settings, error } = await supabase
|
const { data: settings, error } = await supabase
|
||||||
.from('system_settings')
|
.from('system_settings')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('settings_id', 'global')
|
.eq('key', 'global')
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error && error.code !== 'PGRST116') {
|
if (settings && settings.value) {
|
||||||
console.log('System settings table not found, returning defaults');
|
const val = typeof settings.value === 'string' ? JSON.parse(settings.value) : settings.value;
|
||||||
return res.json(DEFAULT_SETTINGS);
|
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) {
|
} catch (error) {
|
||||||
console.error('Error fetching system settings:', error);
|
console.error('Error fetching system settings:', error);
|
||||||
res.json(DEFAULT_SETTINGS);
|
res.json(DEFAULT_SETTINGS);
|
||||||
@@ -33,48 +62,51 @@ export const getSettings = async (req: Request, res: Response) => {
|
|||||||
export const updateSettings = async (req: Request, res: Response) => {
|
export const updateSettings = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { appName, appSubtitle, appLogoUrl } = req.body;
|
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')
|
.from('system_settings')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('settings_id', 'global')
|
.eq('key', 'global')
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
let settings;
|
const valPayload = {
|
||||||
if (fetchError || !existing) {
|
appName: appName || DEFAULT_SETTINGS.appName,
|
||||||
const { data, error } = await supabase
|
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')
|
.from('system_settings')
|
||||||
.insert({
|
.insert({
|
||||||
settings_id: 'global',
|
organization_id: orgId,
|
||||||
app_name: appName,
|
key: 'global',
|
||||||
app_subtitle: appSubtitle,
|
value: valPayload
|
||||||
app_logo_url: appLogoUrl,
|
});
|
||||||
updated_by: req.appUser?.email
|
|
||||||
})
|
|
||||||
.select()
|
|
||||||
.single();
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
settings = data;
|
|
||||||
} else {
|
} else {
|
||||||
const { data, error } = await supabase
|
const { error } = await supabase
|
||||||
.from('system_settings')
|
.from('system_settings')
|
||||||
.update({
|
.update({
|
||||||
app_name: appName,
|
value: valPayload,
|
||||||
app_subtitle: appSubtitle,
|
updated_at: new Date().toISOString()
|
||||||
app_logo_url: appLogoUrl,
|
|
||||||
updated_by: req.appUser?.email
|
|
||||||
})
|
})
|
||||||
.eq('id', existing.id)
|
.eq('id', existing.id);
|
||||||
.select()
|
|
||||||
.single();
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
settings = data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`⚙️ System Settings updated by ${req.appUser?.email}`);
|
console.log(`⚙️ System Settings updated by ${req.appUser?.email}`);
|
||||||
res.json(settings);
|
res.json({
|
||||||
|
settingsId: 'global',
|
||||||
|
appName,
|
||||||
|
appSubtitle,
|
||||||
|
appLogoUrl
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating system settings:', error);
|
console.error('Error updating system settings:', error);
|
||||||
res.status(500).json({ error: 'Erro ao atualizar configurações do sistema' });
|
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 });
|
.order('created_at', { ascending: false });
|
||||||
|
|
||||||
if (error && error.code !== '42P01') throw error;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error getting global users:', error);
|
console.error('Error getting global users:', error);
|
||||||
res.status(500).json({ error: 'Erro ao buscar usuários globais.' });
|
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(
|
const orgsWithMembers = await Promise.all(
|
||||||
organizations.map(async (org) => {
|
organizations.map(async (org) => {
|
||||||
const { data: members } = await supabase
|
let members: any[] = [];
|
||||||
|
try {
|
||||||
|
const { data: mems } = await supabase
|
||||||
.from('user_organizations')
|
.from('user_organizations')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('organization_id', org.id);
|
.eq('organization_id', org.id);
|
||||||
|
members = mems || [];
|
||||||
|
} catch {
|
||||||
|
members = [];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...org,
|
...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
|
memberCount: members?.length || 0
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -73,7 +73,14 @@ export const canEdit = (req: Request, res: Response, next: NextFunction) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const requireDeveloper = (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.' });
|
return res.status(403).json({ error: 'Acesso restrito ao desenvolvedor.' });
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
|
|||||||
Reference in New Issue
Block a user