⚙️ Atualização local para GPI - Sync via Antigravity
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Response } from 'express';
|
||||
import * as appRecordService from '../services/applicationRecordService.js';
|
||||
import '../middleware/roleMiddleware.js'; // Ensure type augmentation
|
||||
import { AuthRequest } from '../middleware/authMiddleware.js';
|
||||
|
||||
export const createApplicationRecord = async (req: Request, res: Response) => {
|
||||
export const createApplicationRecord = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const createdBy = req.appUser?.clerkId;
|
||||
const createdBy = req.appUser?._id?.toString();
|
||||
const record = await appRecordService.createApplicationRecord({ ...req.body, organizationId, createdBy });
|
||||
res.status(201).json(record);
|
||||
} catch (error: unknown) {
|
||||
@@ -14,7 +14,7 @@ export const createApplicationRecord = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getApplicationRecordsByProject = async (req: Request, res: Response) => {
|
||||
export const getApplicationRecordsByProject = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
@@ -26,10 +26,10 @@ export const getApplicationRecordsByProject = async (req: Request, res: Response
|
||||
}
|
||||
};
|
||||
|
||||
export const updateApplicationRecord = async (req: Request, res: Response) => {
|
||||
export const updateApplicationRecord = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const userRole = req.appUser?.organizationRole || req.appUser?.role;
|
||||
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
|
||||
|
||||
@@ -49,10 +49,10 @@ export const updateApplicationRecord = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteApplicationRecord = async (req: Request, res: Response) => {
|
||||
export const deleteApplicationRecord = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const userRole = req.appUser?.organizationRole || req.appUser?.role;
|
||||
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
|
||||
|
||||
|
||||
105
src/server/controllers/authController.ts
Normal file
105
src/server/controllers/authController.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Request, Response } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import User from '../models/User.js';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'secret';
|
||||
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
|
||||
|
||||
export const login = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ error: 'E-mail e senha são obrigatórios' });
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email }).select('+password');
|
||||
|
||||
if (!user || !user.password) {
|
||||
return res.status(401).json({ error: 'Credenciais inválidas' });
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!isMatch) {
|
||||
return res.status(401).json({ error: 'Credenciais inválidas' });
|
||||
}
|
||||
|
||||
if (user.isBanned) {
|
||||
return res.status(403).json({ error: 'Sua conta está bloqueada' });
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user._id, email: user.email, role: user.role },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: JWT_EXPIRES_IN }
|
||||
);
|
||||
|
||||
// Remove password from user object
|
||||
const userObj = user.toObject();
|
||||
delete userObj.password;
|
||||
|
||||
res.json({
|
||||
token,
|
||||
user: userObj
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ error: 'Erro interno no servidor' });
|
||||
}
|
||||
};
|
||||
|
||||
export const register = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { email, password, name, organizationId } = req.body;
|
||||
|
||||
if (!email || !password || !name) {
|
||||
return res.status(400).json({ error: 'Campos obrigatórios ausentes' });
|
||||
}
|
||||
|
||||
const existingUser = await User.findOne({ email });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ error: 'E-mail já cadastrado' });
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
|
||||
const user = await User.create({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
name,
|
||||
organizationId,
|
||||
role: 'user' // Default role
|
||||
});
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user._id, email: user.email, role: user.role },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: JWT_EXPIRES_IN }
|
||||
);
|
||||
|
||||
const userObj = user.toObject();
|
||||
delete userObj.password;
|
||||
|
||||
res.status(201).json({
|
||||
token,
|
||||
user: userObj
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
res.status(500).json({ error: 'Erro ao criar usuário' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getMe = async (req: any, res: Response) => {
|
||||
try {
|
||||
const user = await User.findById(req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'Usuário não encontrado' });
|
||||
}
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Erro ao obter dados do usuário' });
|
||||
}
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import '../middleware/roleMiddleware.js'; // Ensure type augmentation
|
||||
export const createInspection = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const createdBy = req.appUser?.clerkId;
|
||||
const createdBy = req.appUser?._id?.toString();
|
||||
const inspection = await inspectionService.createInspection({
|
||||
...req.body,
|
||||
organizationId,
|
||||
@@ -46,7 +46,7 @@ export const getInspectionsByProject = async (req: Request, res: Response) => {
|
||||
export const updateInspection = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const userRole = req.appUser?.organizationRole || req.appUser?.role;
|
||||
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
|
||||
|
||||
@@ -69,7 +69,7 @@ export const updateInspection = async (req: Request, res: Response) => {
|
||||
export const deleteInspection = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const userRole = req.appUser?.organizationRole || req.appUser?.role;
|
||||
const isDeveloper = req.appUser?.email === 'admtracksteel@gmail.com';
|
||||
|
||||
@@ -99,7 +99,7 @@ export const getAllInspections = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadPhoto = async (req: Request, res: Response) => {
|
||||
export const uploadPhoto = async (req: Request & { file?: any }, res: Response) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'No file uploaded' });
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Response } from 'express';
|
||||
import Message from '../models/Message.js';
|
||||
import OrganizationMember from '../models/OrganizationMember.js';
|
||||
import { AuthRequest } from '../middleware/authMiddleware.js';
|
||||
|
||||
// Send a message
|
||||
export const sendMessage = async (req: Request, res: Response) => {
|
||||
export const sendMessage = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { toUserId, message } = req.body;
|
||||
const fromUserId = req.appUser?.clerkId;
|
||||
const fromUserId = req.appUser?._id?.toString();
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
if (!organizationId) {
|
||||
@@ -58,9 +59,9 @@ export const sendMessage = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// Get unread messages for current user
|
||||
export const getUnreadMessages = async (req: Request, res: Response) => {
|
||||
export const getUnreadMessages = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const toUserId = req.appUser?.clerkId;
|
||||
const toUserId = req.appUser?._id?.toString();
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
if (!organizationId) {
|
||||
@@ -82,7 +83,7 @@ export const getUnreadMessages = async (req: Request, res: Response) => {
|
||||
// Populate sender info
|
||||
const messagesWithSender = await Promise.all(
|
||||
messages.map(async (msg) => {
|
||||
const sender = await OrganizationMember.findOne({ clerkUserId: msg.fromUserId });
|
||||
const sender = await OrganizationMember.findOne({ userId: msg.fromUserId });
|
||||
return {
|
||||
...msg.toObject(),
|
||||
fromUser: sender ? { name: sender.name, email: sender.email } : null,
|
||||
@@ -98,10 +99,10 @@ export const getUnreadMessages = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// Mark message as read
|
||||
export const markMessageAsRead = async (req: Request, res: Response) => {
|
||||
export const markMessageAsRead = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
if (!organizationId) {
|
||||
@@ -134,9 +135,9 @@ export const markMessageAsRead = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// Get my pending (unread) sent messages
|
||||
export const getMyPendingMessages = async (req: Request, res: Response) => {
|
||||
export const getMyPendingMessages = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const fromUserId = req.appUser?.clerkId;
|
||||
const fromUserId = req.appUser?._id?.toString();
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
if (!organizationId) {
|
||||
@@ -156,7 +157,7 @@ export const getMyPendingMessages = async (req: Request, res: Response) => {
|
||||
// Populate recipient info
|
||||
const messagesWithRecipient = await Promise.all(
|
||||
messages.map(async (msg) => {
|
||||
const recipient = await OrganizationMember.findOne({ clerkUserId: msg.toUserId });
|
||||
const recipient = await OrganizationMember.findOne({ userId: msg.toUserId });
|
||||
return {
|
||||
...msg.toObject(),
|
||||
toUser: recipient ? { name: recipient.name, email: recipient.email } : null,
|
||||
@@ -172,10 +173,10 @@ export const getMyPendingMessages = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// Delete a message (only if unread and sender is the current user)
|
||||
export const deleteMessage = async (req: Request, res: Response) => {
|
||||
export const deleteMessage = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
if (!organizationId) {
|
||||
@@ -206,10 +207,10 @@ export const deleteMessage = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// Recipient deletes/archives a message
|
||||
export const archiveMessage = async (req: Request, res: Response) => {
|
||||
export const archiveMessage = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
const message = await Message.findOne({ _id: id, toUserId: userId, organizationId });
|
||||
@@ -225,10 +226,10 @@ export const archiveMessage = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const recipientDeleteMessage = async (req: Request, res: Response) => {
|
||||
export const recipientDeleteMessage = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.appUser?.clerkId;
|
||||
const userId = req.appUser?._id?.toString();
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
const message = await Message.findOne({ _id: id, toUserId: userId, organizationId });
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { notificationService } from '../services/notificationService.js';
|
||||
import { AuthRequest } from '../middleware/authMiddleware.js';
|
||||
|
||||
export const notificationController = {
|
||||
getUserNotifications: async (req: Request, res: Response) => {
|
||||
getUserNotifications: async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
const userId = req.headers['x-user-id'] as string; // Assumindo que temos o ID do usuário (clerkId ou email)
|
||||
|
||||
// Se não tiver userId no header (ainda não implementado auth full), tentar pegar do query ou usar um fallback
|
||||
// Nota: Idealmente o middleware de auth popula req.user. Vamos assumir que passamos x-user-id no frontend por enquanto.
|
||||
const userId = req.appUser?._id?.toString() || '';
|
||||
|
||||
if (!organizationId) {
|
||||
return res.status(400).json({ error: 'Organization ID is required' });
|
||||
@@ -26,7 +24,7 @@ export const notificationController = {
|
||||
}
|
||||
},
|
||||
|
||||
markAsRead: async (req: Request, res: Response) => {
|
||||
markAsRead: async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const notification = await notificationService.markAsRead(id as string);
|
||||
@@ -37,10 +35,10 @@ export const notificationController = {
|
||||
}
|
||||
},
|
||||
|
||||
markAllAsRead: async (req: Request, res: Response) => {
|
||||
markAllAsRead: async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
const userId = req.headers['x-user-id'] as string;
|
||||
const userId = req.appUser?._id?.toString() || '';
|
||||
|
||||
if (!organizationId) {
|
||||
return res.status(400).json({ error: 'Organization ID is required' });
|
||||
@@ -54,10 +52,10 @@ export const notificationController = {
|
||||
}
|
||||
},
|
||||
|
||||
clearAll: async (req: Request, res: Response) => {
|
||||
clearAll: async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
const userId = req.headers['x-user-id'] as string;
|
||||
const userId = req.appUser?._id?.toString() || '';
|
||||
|
||||
if (!organizationId) {
|
||||
return res.status(400).json({ error: 'Organization ID is required' });
|
||||
@@ -71,10 +69,10 @@ export const notificationController = {
|
||||
}
|
||||
},
|
||||
|
||||
archive: async (req: Request, res: Response) => {
|
||||
archive: async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.headers['x-user-id'] as string;
|
||||
const userId = req.appUser?._id?.toString() || '';
|
||||
const notification = await notificationService.archive(id as string, userId);
|
||||
res.json(notification);
|
||||
} catch (error) {
|
||||
@@ -83,10 +81,10 @@ export const notificationController = {
|
||||
}
|
||||
},
|
||||
|
||||
delete: async (req: Request, res: Response) => {
|
||||
delete: async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.headers['x-user-id'] as string;
|
||||
const userId = req.appUser?._id?.toString() || '';
|
||||
await notificationService.softDelete(id as string, userId);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
|
||||
@@ -67,7 +67,7 @@ export const createStockItem = async (req: AuthRequest, res: Response) => {
|
||||
|
||||
const newItem = new StockItem({
|
||||
organizationId,
|
||||
createdBy: req.appUser?.clerkId,
|
||||
createdBy: req.appUser?._id,
|
||||
dataSheetId,
|
||||
rrNumber,
|
||||
batchNumber,
|
||||
@@ -86,7 +86,7 @@ export const createStockItem = async (req: AuthRequest, res: Response) => {
|
||||
// Create Initial Movement (ENTRY)
|
||||
await StockMovement.create({
|
||||
organizationId,
|
||||
createdBy: req.appUser?.clerkId,
|
||||
createdBy: req.appUser?._id,
|
||||
stockItemId: savedItem._id,
|
||||
movementNumber: 1,
|
||||
type: 'ENTRY',
|
||||
@@ -195,7 +195,7 @@ export const adjustStock = async (req: AuthRequest, res: Response) => {
|
||||
// Register Movement
|
||||
await StockMovement.create({
|
||||
organizationId,
|
||||
createdBy: req.appUser?.clerkId,
|
||||
createdBy: req.appUser?._id,
|
||||
stockItemId: item._id,
|
||||
movementNumber,
|
||||
type: 'ADJUSTMENT',
|
||||
@@ -241,7 +241,7 @@ export const consumeStock = async (req: AuthRequest, res: Response) => {
|
||||
// Register Movement (Negative quantity for consumption)
|
||||
await StockMovement.create({
|
||||
organizationId,
|
||||
createdBy: req.appUser?.clerkId,
|
||||
createdBy: req.appUser?._id,
|
||||
stockItemId: item._id,
|
||||
movementNumber,
|
||||
type: 'CONSUMPTION',
|
||||
@@ -348,7 +348,7 @@ export const updateStockMovement = async (req: AuthRequest, res: Response) => {
|
||||
const { id } = req.params; // Movement ID
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
|
||||
const userId = req.appUser?.clerkId || 'system';
|
||||
const userId = req.appUser?._id || 'system';
|
||||
const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';
|
||||
|
||||
if (!isAdmin) {
|
||||
@@ -431,7 +431,7 @@ export const deleteStockMovement = async (req: AuthRequest, res: Response) => {
|
||||
const { id } = req.params;
|
||||
const organizationId = req.appUser?.organizationId;
|
||||
const userName = req.appUser?.name || req.appUser?.email || 'Unknown User';
|
||||
const userId = req.appUser?.clerkId || 'system';
|
||||
const userId = req.appUser?._id || 'system';
|
||||
const isAdmin = req.appUser?.role === 'admin' || req.appUser?.organizationRole === 'admin';
|
||||
|
||||
if (!isAdmin) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Response } from 'express';
|
||||
import SystemSettings from '../models/SystemSettings.js';
|
||||
import User from '../models/User.js';
|
||||
import OrganizationMember from '../models/OrganizationMember.js';
|
||||
@@ -6,8 +6,9 @@ import Organization from '../models/Organization.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { AuthRequest } from '../middleware/authMiddleware.js';
|
||||
|
||||
export const getSettings = async (req: Request, res: Response) => {
|
||||
export const getSettings = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
let settings = await SystemSettings.findOne({ settingsId: 'global' });
|
||||
|
||||
@@ -27,7 +28,7 @@ export const getSettings = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateSettings = async (req: Request, res: Response) => {
|
||||
export const updateSettings = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { appName, appSubtitle, appLogoUrl } = req.body;
|
||||
|
||||
@@ -51,7 +52,7 @@ export const updateSettings = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
|
||||
export const serveLogo = async (req: Request, res: Response) => {
|
||||
export const serveLogo = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { filename } = req.params as { filename: string };
|
||||
|
||||
@@ -74,7 +75,7 @@ export const serveLogo = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadLogo = async (req: Request, res: Response) => {
|
||||
export const uploadLogo = async (req: AuthRequest & { file?: any }, res: Response) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'Nenhum arquivo enviado.' });
|
||||
@@ -92,7 +93,7 @@ export const uploadLogo = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// Global Admin Functions
|
||||
export const getGlobalUsers = async (req: Request, res: Response) => {
|
||||
export const getGlobalUsers = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const users = await User.find({}).sort({ createdAt: -1 });
|
||||
res.json(users);
|
||||
@@ -102,7 +103,7 @@ export const getGlobalUsers = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getGlobalOrganizations = async (req: Request, res: Response) => {
|
||||
export const getGlobalOrganizations = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
// Aggregate members to group by org and get full member lists
|
||||
const organizations = await OrganizationMember.aggregate([
|
||||
@@ -111,10 +112,10 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
|
||||
_id: '$organizationId',
|
||||
members: {
|
||||
$push: {
|
||||
id: '$userId',
|
||||
name: '$name',
|
||||
email: '$email',
|
||||
role: '$role',
|
||||
clerkUserId: '$clerkUserId',
|
||||
isBanned: '$isBanned'
|
||||
}
|
||||
},
|
||||
@@ -125,7 +126,7 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
|
||||
$lookup: {
|
||||
from: 'organizations', // Ensure this matches the collection name of Organization model
|
||||
localField: '_id',
|
||||
foreignField: 'clerkId',
|
||||
foreignField: 'organizationId', // We should rename clerkId in Organization model too
|
||||
as: 'orgDetails'
|
||||
}
|
||||
},
|
||||
@@ -155,7 +156,7 @@ export const getGlobalOrganizations = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleOrganizationBan = async (req: Request, res: Response) => {
|
||||
export const toggleOrganizationBan = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { organizationId, isBanned } = req.body;
|
||||
|
||||
@@ -165,7 +166,7 @@ export const toggleOrganizationBan = async (req: Request, res: Response) => {
|
||||
|
||||
// Upsert the Organization record
|
||||
const org = await Organization.findOneAndUpdate(
|
||||
{ clerkId: organizationId },
|
||||
{ organizationId: organizationId },
|
||||
{ isBanned: isBanned },
|
||||
{ new: true, upsert: true }
|
||||
);
|
||||
|
||||
@@ -1,101 +1,9 @@
|
||||
import { Request, Response } from 'express';
|
||||
import User, { IUser } from '../models/User.js';
|
||||
import OrganizationMember, { OrgRole } from '../models/OrganizationMember.js';
|
||||
import { AuthRequest } from '../middleware/authMiddleware.js';
|
||||
|
||||
// Define locally to avoid import cycle risks
|
||||
interface IAppUser extends IUser {
|
||||
organizationId?: string;
|
||||
organizationRole?: OrgRole;
|
||||
organizationBanned?: boolean;
|
||||
}
|
||||
|
||||
interface AuthRequest extends Request {
|
||||
appUser?: IAppUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync user from Clerk to MongoDB
|
||||
* Creates user if doesn't exist, updates if exists
|
||||
* Also creates/updates OrganizationMember for the current organization
|
||||
*/
|
||||
export const syncUser = async (req: Request, res: Response) => {
|
||||
console.log('--- syncUser called ---', req.body);
|
||||
try {
|
||||
const { clerkId, email, name, organizationId, clerkRole } = req.body;
|
||||
|
||||
if (!clerkId || !email || !name) {
|
||||
return res.status(400).json({ error: 'clerkId, email e name são obrigatórios.' });
|
||||
}
|
||||
|
||||
// 1. Upsert the global User record
|
||||
let user = await User.findOne({ clerkId });
|
||||
|
||||
if (user) {
|
||||
user.email = email;
|
||||
user.name = name;
|
||||
await user.save();
|
||||
} else {
|
||||
user = await User.create({
|
||||
clerkId,
|
||||
email,
|
||||
name,
|
||||
role: 'guest', // Default global role
|
||||
isBanned: false
|
||||
});
|
||||
}
|
||||
|
||||
if (organizationId) {
|
||||
|
||||
// Map Clerk role to our app role
|
||||
let appRole: OrgRole = 'guest';
|
||||
if (clerkRole === 'org:admin') {
|
||||
appRole = 'admin';
|
||||
} else if (clerkRole === 'org:member') {
|
||||
appRole = 'user';
|
||||
}
|
||||
|
||||
// Use findOneAndUpdate with upsert to handle race conditions atomically
|
||||
// This avoids the need for try/catch on create and handles existing members too
|
||||
const member = await OrganizationMember.findOneAndUpdate(
|
||||
{ clerkUserId: clerkId, organizationId },
|
||||
{
|
||||
$set: {
|
||||
name,
|
||||
email,
|
||||
// Only update role if it's the first time (creation)
|
||||
// Or we can optionally update it if needed.
|
||||
// For now, let's NOT overwrite role on update to preserve local changes,
|
||||
// UNLESS we want to force sync with Clerk.
|
||||
// Let's use $setOnInsert for fields we only want to set on creation.
|
||||
},
|
||||
$setOnInsert: {
|
||||
role: appRole,
|
||||
isBanned: false
|
||||
}
|
||||
},
|
||||
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
||||
);
|
||||
|
||||
// Return combined info
|
||||
return res.json({
|
||||
...user.toObject(),
|
||||
organizationRole: member.role,
|
||||
organizationBanned: member.isBanned
|
||||
});
|
||||
}
|
||||
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
console.error('Error syncing user:', error);
|
||||
// Retornar 200 mesmo com erro para não travar o frontend se for algo não crítico,
|
||||
// mas aqui é crítico. Vamos logar melhor.
|
||||
res.status(500).json({ error: 'Erro ao sincronizar usuário: ' + (error instanceof Error ? error.message : String(error)) });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current user data with organization context
|
||||
*/
|
||||
// Get current user data with organization context
|
||||
export const getCurrentUser = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
if (!req.appUser) {
|
||||
@@ -106,7 +14,7 @@ export const getCurrentUser = async (req: AuthRequest, res: Response) => {
|
||||
|
||||
if (organizationId) {
|
||||
const member = await OrganizationMember.findOne({
|
||||
clerkUserId: req.appUser.clerkId,
|
||||
userId: req.appUser._id.toString(),
|
||||
organizationId
|
||||
});
|
||||
|
||||
@@ -127,21 +35,16 @@ export const getCurrentUser = async (req: AuthRequest, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all users for the current organization (admin only)
|
||||
*/
|
||||
// Get all users for the current organization (admin only)
|
||||
export const getAllUsers = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
|
||||
console.log('getAllUsers called with organizationId:', organizationId);
|
||||
|
||||
if (!organizationId) {
|
||||
return res.status(400).json({ error: 'Organização não selecionada.' });
|
||||
}
|
||||
|
||||
const members = await OrganizationMember.find({ organizationId }).sort({ createdAt: -1 });
|
||||
console.log(`Found ${members.length} members for org ${organizationId}:`, members.map(m => ({ name: m.name, email: m.email, clerkId: m.clerkUserId })));
|
||||
res.json(members);
|
||||
} catch (error) {
|
||||
console.error('Error getting users:', error);
|
||||
@@ -149,9 +52,7 @@ export const getAllUsers = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user role within organization (admin only)
|
||||
*/
|
||||
// Update user role within organization (admin only)
|
||||
export const updateUserRole = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
@@ -184,14 +85,12 @@ export const updateUserRole = async (req: AuthRequest, res: Response) => {
|
||||
|
||||
res.json(member);
|
||||
} catch (error) {
|
||||
console.error('Error toggling ban:', error);
|
||||
res.status(500).json({ error: 'Erro ao alterar status de banimento.' });
|
||||
console.error('Error updating role:', error);
|
||||
res.status(500).json({ error: 'Erro ao atualizar role.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ban or unban user within organization (admin only)
|
||||
*/
|
||||
// Ban or unban user within organization (admin only)
|
||||
export const toggleBanUser = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
@@ -208,12 +107,12 @@ export const toggleBanUser = async (req: AuthRequest, res: Response) => {
|
||||
}
|
||||
|
||||
// Prevent banning yourself
|
||||
if (req.appUser && member.clerkUserId === req.appUser.clerkId) {
|
||||
if (req.appUser && member.userId === req.appUser._id.toString()) {
|
||||
return res.status(400).json({ error: 'Você não pode banir a si mesmo.' });
|
||||
}
|
||||
|
||||
// Prevent banning another admin
|
||||
if (member.role === 'admin') {
|
||||
if (member.role === 'admin' && isBanned) {
|
||||
return res.status(400).json({ error: 'Não é possível banir um administrador.' });
|
||||
}
|
||||
|
||||
@@ -227,38 +126,22 @@ export const toggleBanUser = async (req: AuthRequest, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update current user's lastSeenAt timestamp
|
||||
*/
|
||||
// Update current user's lastSeenAt timestamp
|
||||
export const heartbeat = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
if (!req.appUser) {
|
||||
return res.status(401).json({ error: 'Não autenticado.' });
|
||||
}
|
||||
|
||||
// Update User model
|
||||
await User.findByIdAndUpdate(req.appUser._id, { lastSeenAt: new Date() });
|
||||
|
||||
// Also update Organization Member for tighter query
|
||||
// But for now User model is enough if we join correctly, or just use User model for presence.
|
||||
// Actually, since we want to show users per organization, we should filter by Org.
|
||||
// Our 'User.ts' has organizationId, but it might be just the 'default' one.
|
||||
// Let's rely on OrganizationMember for the list, but we need to update lastSeenAt there too?
|
||||
// Strategy: Update User (global), and when querying active users, join or filter.
|
||||
// Better: Update OrganizationMember too if we want org-specific presence?
|
||||
// Simpler: Just update User. When fetching active users, we fetch OrganizationMembers and populate User details, filtering by User.lastSeenAt.
|
||||
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
// Silent fail for heartbeat
|
||||
console.error('Heartbeat error:', error);
|
||||
res.status(500).send();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get active users in the same organization (seen in last 2 mins)
|
||||
*/
|
||||
// Get active users in the same organization (seen in last 2 mins)
|
||||
export const getActiveUsers = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const organizationId = req.headers['x-organization-id'] as string;
|
||||
@@ -268,20 +151,15 @@ export const getActiveUsers = async (req: AuthRequest, res: Response) => {
|
||||
return res.status(400).json([]);
|
||||
}
|
||||
|
||||
// Find members of this org
|
||||
const members = await OrganizationMember.find({ organizationId });
|
||||
const userIds = members.map(m => m.userId);
|
||||
|
||||
// Get their Clerk IDs
|
||||
const clerkIds = members.map(m => m.clerkUserId);
|
||||
|
||||
// Find Users who were seen recently (2 minutes)
|
||||
const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);
|
||||
|
||||
const activeUsers = await User.find({
|
||||
clerkId: { $in: clerkIds },
|
||||
lastSeenAt: { $gte: twoMinutesAgo },
|
||||
_id: { $ne: currentUserId } // Optional: exclude self
|
||||
}).select('name email lastSeenAt clerkId'); // Only needed fields
|
||||
_id: { $in: userIds, $ne: currentUserId },
|
||||
lastSeenAt: { $gte: twoMinutesAgo }
|
||||
}).select('name email lastSeenAt');
|
||||
|
||||
res.json(activeUsers);
|
||||
} catch (error) {
|
||||
@@ -300,20 +178,20 @@ export const deleteUser = async (req: Request, res: Response) => {
|
||||
return res.status(400).json({ error: 'Organização não selecionada.' });
|
||||
}
|
||||
|
||||
console.log(`Deleting member ${id} from organization ${organizationId}`);
|
||||
|
||||
// Delete from OrganizationMember collection
|
||||
const result = await OrganizationMember.findByIdAndDelete(id);
|
||||
const result = await OrganizationMember.findOneAndDelete({ _id: id, organizationId });
|
||||
|
||||
if (!result) {
|
||||
return res.status(404).json({ error: 'Membro não encontrado.' });
|
||||
}
|
||||
|
||||
console.log(`Member ${result.name} deleted successfully`);
|
||||
|
||||
res.json({ message: 'Membro removido com sucesso.', deletedMember: result });
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
res.status(500).json({ error: 'Erro ao remover membro.' });
|
||||
}
|
||||
};
|
||||
|
||||
// Placeholder for sync (not needed with custom auth but keeping to avoid breaks if called)
|
||||
export const syncUser = async (req: Request, res: Response) => {
|
||||
res.json({ message: 'Sync no longer required with JWT auth.' });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user