backlinks-api / src /controllers /threads.js
Abhises's picture
added rejection limit
80917de
Raw History Blame Contribute Delete
8.38 kB
const prisma = require('../lib/prisma');
const wsManager = require('../lib/ws');
// Helper: get user's workspace
async function getUserWorkspace(userId) {
const member = await prisma.teamMember.findFirst({
where: { userId },
include: { workspace: true },
});
return member;
}
const createThread = async (req, res) => {
const { receiverWorkspaceId } = req.body;
if (!receiverWorkspaceId) {
return res.status(400).json({ error: 'Receiver workspace ID required' });
}
try {
const member = await getUserWorkspace(req.user.userId);
if (!member) return res.status(404).json({ error: 'You have no workspace' });
if (member.workspaceId === receiverWorkspaceId) {
return res.status(400).json({ error: 'Cannot send request to your own workspace' });
}
// Check for existing thread
const existing = await prisma.exchangeThread.findFirst({
where: {
OR: [
{ giverWorkspaceId: member.workspaceId, receiverWorkspaceId },
{ giverWorkspaceId: receiverWorkspaceId, receiverWorkspaceId: member.workspaceId },
],
},
});
if (existing) return res.status(409).json({ error: 'Thread already exists between these workspaces' });
const thread = await prisma.exchangeThread.create({
data: {
giverWorkspaceId: member.workspaceId,
receiverWorkspaceId,
stage: 'NEW',
status: 'PENDING',
giverAccepted: true,
},
include: {
giverWorkspace: true,
receiverWorkspace: true,
},
});
wsManager.sendNotification(receiverWorkspaceId, {
type: 'new_connection',
threadId: thread.id,
senderWorkspaceName: thread.giverWorkspace.websiteName,
senderWorkspaceDomain: thread.giverWorkspace.domain,
});
res.status(201).json({ thread });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Server error' });
}
};
const getThreads = async (req, res) => {
const { filter } = req.query; // all | new | in | out
try {
const member = await getUserWorkspace(req.user.userId);
if (!member) return res.status(404).json({ error: 'No workspace found' });
const wsId = member.workspaceId;
let where = {
OR: [{ giverWorkspaceId: wsId }, { receiverWorkspaceId: wsId }],
status: { not: 'REJECTED' },
};
if (filter === 'new') where.stage = 'NEW';
else if (filter === 'in') {
where = { receiverWorkspaceId: wsId, status: { not: 'REJECTED' } };
}
else if (filter === 'out') {
where = { giverWorkspaceId: wsId, status: { not: 'REJECTED' } };
}
else if (filter === 'rejected') {
where = { rejectedByWorkspaceId: wsId, status: 'REJECTED' };
}
const threads = await prisma.exchangeThread.findMany({
where,
include: {
giverWorkspace: {
select: {
id: true, domain: true, websiteName: true, niche: true, country: true, description: true, createdAt: true,
teamMembers: { where: { role: 'OWNER' }, select: { user: { select: { name: true } } } }
}
},
receiverWorkspace: {
select: {
id: true, domain: true, websiteName: true, niche: true, country: true, description: true, createdAt: true,
teamMembers: { where: { role: 'OWNER' }, select: { user: { select: { name: true } } } }
}
},
messages: { orderBy: { timestamp: 'desc' }, take: 1 },
linkPlacement: true,
},
orderBy: { updatedAt: 'desc' },
});
let settings = await prisma.systemSettings.findUnique({ where: { id: 'singleton' } });
const rejectLimit = settings ? settings.rejectLimit : 5;
res.json({ threads, workspaceId: wsId, rejectLimit });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Server error' });
}
};
const getThreadById = async (req, res) => {
try {
const member = await getUserWorkspace(req.user.userId);
if (!member) return res.status(404).json({ error: 'No workspace found' });
const thread = await prisma.exchangeThread.findUnique({
where: { id: req.params.id },
include: {
giverWorkspace: {
include: { teamMembers: { where: { role: 'OWNER' }, select: { user: { select: { name: true } } } } }
},
receiverWorkspace: {
include: { teamMembers: { where: { role: 'OWNER' }, select: { user: { select: { name: true } } } } }
},
messages: {
orderBy: { timestamp: 'asc' },
include: { sender: { select: { id: true, name: true, email: true } } },
},
linkPlacement: true,
},
});
if (!thread) return res.status(404).json({ error: 'Thread not found' });
// Verify access
if (thread.giverWorkspaceId !== member.workspaceId && thread.receiverWorkspaceId !== member.workspaceId) {
return res.status(403).json({ error: 'Access denied' });
}
res.json({ thread, workspaceId: member.workspaceId });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Server error' });
}
};
const updateThreadStatus = async (req, res) => {
const { status } = req.body; // ACCEPTED | REJECTED
if (!['ACCEPTED', 'REJECTED'].includes(status)) {
return res.status(400).json({ error: 'Status must be ACCEPTED or REJECTED' });
}
try {
const member = await getUserWorkspace(req.user.userId);
if (!member) return res.status(404).json({ error: 'No workspace found' });
const thread = await prisma.exchangeThread.findUnique({ where: { id: req.params.id } });
if (!thread) return res.status(404).json({ error: 'Thread not found' });
const isGiver = thread.giverWorkspaceId === member.workspaceId;
const isReceiver = thread.receiverWorkspaceId === member.workspaceId;
if (!isGiver && !isReceiver) {
return res.status(403).json({ error: 'Access denied' });
}
let updateData = {};
let newStatus = thread.status;
let newStage = thread.stage;
let giverAccepted = thread.giverAccepted;
let receiverAccepted = thread.receiverAccepted;
if (status === 'REJECTED') {
let settings = await prisma.systemSettings.findUnique({ where: { id: 'singleton' } });
const rejectLimit = settings ? settings.rejectLimit : 5;
const rejectedCount = await prisma.exchangeThread.count({
where: { rejectedByWorkspaceId: member.workspaceId, status: 'REJECTED' }
});
if (rejectedCount >= rejectLimit) {
return res.status(403).json({ error: `Cannot reject more than ${rejectLimit} requests` });
}
newStatus = 'REJECTED';
newStage = 'NEW';
updateData = { status: newStatus, stage: newStage, rejectedByWorkspaceId: member.workspaceId };
} else if (status === 'ACCEPTED') {
if (isGiver) giverAccepted = true;
if (isReceiver) receiverAccepted = true;
updateData = { giverAccepted, receiverAccepted };
if (giverAccepted && receiverAccepted) {
newStatus = 'ACCEPTED';
newStage = 'CHAT';
updateData.status = newStatus;
updateData.stage = newStage;
}
}
const updated = await prisma.exchangeThread.update({
where: { id: req.params.id },
data: updateData,
include: {
giverWorkspace: true,
receiverWorkspace: true,
},
});
// Determine who to notify
const otherId = isGiver ? thread.receiverWorkspaceId : thread.giverWorkspaceId;
const selfWorkspace = isGiver ? updated.giverWorkspace : updated.receiverWorkspace;
if (newStatus === 'ACCEPTED' && newStage === 'CHAT') {
wsManager.sendNotification(otherId, {
type: 'connection_accepted',
threadId: updated.id,
receiverWorkspaceName: selfWorkspace.websiteName,
receiverWorkspaceDomain: selfWorkspace.domain,
});
} else if (newStatus === 'REJECTED') {
wsManager.sendNotification(otherId, {
type: 'connection_rejected',
threadId: updated.id,
receiverWorkspaceName: selfWorkspace.websiteName,
receiverWorkspaceDomain: selfWorkspace.domain,
});
}
res.json({ thread: updated });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Server error' });
}
};
module.exports = {
createThread,
getThreads,
getThreadById,
updateThreadStatus,
};