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">
|
||||
{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(
|
||||
|
||||
@@ -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
|
||||
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
|
||||
};
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user