Expose manager telegram avatars

This commit is contained in:
Ruslan Bakiev
2026-04-04 10:13:50 +07:00
parent 6b966c763e
commit 335ba994ab
4 changed files with 136 additions and 97 deletions

99
src/access.js Normal file
View File

@@ -0,0 +1,99 @@
export const MANAGER_ROLES = ['MANAGER', 'SUPER_MANAGER'];
const NO_CLIENT_IDS = ['__no_managed_clients__'];
export function isSuperManager(user) {
return user?.role === 'SUPER_MANAGER';
}
export function isManagerRole(role) {
return MANAGER_ROLES.includes(role);
}
function normalizeManagedClientIds(clientIds) {
if (clientIds == null) {
return null;
}
return clientIds.length ? clientIds : NO_CLIENT_IDS;
}
export async function getManagedClientIds(prisma, manager) {
if (isSuperManager(manager)) {
return null;
}
const [managedOrders, acceptedInvitations, reviewedRequests, reviewedWithdrawals] = await Promise.all([
prisma.order.findMany({
where: { managerId: manager.id },
select: { customerId: true },
}),
prisma.invitation.findMany({
where: {
managerId: manager.id,
acceptedById: { not: null },
},
select: { acceptedById: true },
}),
prisma.registrationRequest.findMany({
where: {
reviewedById: manager.id,
requesterId: { not: null },
},
select: { requesterId: true },
}),
prisma.rewardWithdrawalRequest.findMany({
where: { reviewedById: manager.id },
select: { requesterId: true },
}),
]);
const clientIds = new Set();
for (const order of managedOrders) {
if (order.customerId) {
clientIds.add(order.customerId);
}
}
for (const invitation of acceptedInvitations) {
if (invitation.acceptedById) {
clientIds.add(invitation.acceptedById);
}
}
for (const request of reviewedRequests) {
if (request.requesterId) {
clientIds.add(request.requesterId);
}
}
for (const withdrawal of reviewedWithdrawals) {
if (withdrawal.requesterId) {
clientIds.add(withdrawal.requesterId);
}
}
return [...clientIds];
}
export async function getManagedClientUserWhere(prisma, manager) {
const managedClientIds = normalizeManagedClientIds(await getManagedClientIds(prisma, manager));
if (managedClientIds == null) {
return { role: 'CLIENT' };
}
return {
role: 'CLIENT',
id: { in: managedClientIds },
};
}
export async function canManagerAccessUser(prisma, manager, userId) {
if (isSuperManager(manager) || userId === manager.id) {
return true;
}
const managedClientIds = await getManagedClientIds(prisma, manager);
return managedClientIds.includes(userId);
}

View File

@@ -8,14 +8,19 @@ import {
maskAuthDestination, maskAuthDestination,
verifyLoginChallengeCode, verifyLoginChallengeCode,
} from './auth.js'; } from './auth.js';
import {
MANAGER_ROLES,
canManagerAccessUser,
getManagedClientUserWhere,
isManagerRole,
isSuperManager,
} from './access.js';
import { sendLoginCodeEmail } from './mailer.js'; import { sendLoginCodeEmail } from './mailer.js';
import { dispatchToUserConnections, sendMessengerMessage } from './messenger.js'; import { dispatchToUserConnections, sendMessengerMessage } from './messenger.js';
import { dateTimeScalar, jsonScalar } from './scalars.js'; import { dateTimeScalar, jsonScalar } from './scalars.js';
import { fetchTelegramConnectionProfile } from './telegram.js'; import { fetchTelegramConnectionProfile } from './telegram.js';
const ACTIVE_ORDER_STATUSES = ['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED', 'IN_PROGRESS']; const ACTIVE_ORDER_STATUSES = ['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED', 'IN_PROGRESS'];
const MANAGER_ROLES = ['MANAGER', 'SUPER_MANAGER'];
const NO_CLIENT_IDS = ['__no_managed_clients__'];
function toFloat(value) { function toFloat(value) {
return value == null ? null : Number(value); return value == null ? null : Number(value);
@@ -40,101 +45,8 @@ function requireManagerAccess(context) {
return requireAnyRole(context, MANAGER_ROLES); return requireAnyRole(context, MANAGER_ROLES);
} }
function isSuperManager(user) {
return user.role === 'SUPER_MANAGER';
}
function isManagerRole(role) {
return MANAGER_ROLES.includes(role);
}
function normalizeManagedClientIds(clientIds) {
if (clientIds == null) {
return null;
}
return clientIds.length ? clientIds : NO_CLIENT_IDS;
}
async function getManagedClientIds(prisma, manager) {
if (isSuperManager(manager)) {
return null;
}
const [managedOrders, acceptedInvitations, reviewedRequests, reviewedWithdrawals] = await Promise.all([
prisma.order.findMany({
where: { managerId: manager.id },
select: { customerId: true },
}),
prisma.invitation.findMany({
where: {
managerId: manager.id,
acceptedById: { not: null },
},
select: { acceptedById: true },
}),
prisma.registrationRequest.findMany({
where: {
reviewedById: manager.id,
requesterId: { not: null },
},
select: { requesterId: true },
}),
prisma.rewardWithdrawalRequest.findMany({
where: { reviewedById: manager.id },
select: { requesterId: true },
}),
]);
const clientIds = new Set();
for (const order of managedOrders) {
if (order.customerId) {
clientIds.add(order.customerId);
}
}
for (const invitation of acceptedInvitations) {
if (invitation.acceptedById) {
clientIds.add(invitation.acceptedById);
}
}
for (const request of reviewedRequests) {
if (request.requesterId) {
clientIds.add(request.requesterId);
}
}
for (const withdrawal of reviewedWithdrawals) {
if (withdrawal.requesterId) {
clientIds.add(withdrawal.requesterId);
}
}
return [...clientIds];
}
async function getManagedClientUserWhere(prisma, manager) {
const managedClientIds = normalizeManagedClientIds(await getManagedClientIds(prisma, manager));
if (managedClientIds == null) {
return { role: 'CLIENT' };
}
return {
role: 'CLIENT',
id: { in: managedClientIds },
};
}
async function assertManagerCanAccessUser(prisma, manager, userId) { async function assertManagerCanAccessUser(prisma, manager, userId) {
if (isSuperManager(manager) || userId === manager.id) { if (!await canManagerAccessUser(prisma, manager, userId)) {
return;
}
const managedClientIds = await getManagedClientIds(prisma, manager);
if (!managedClientIds.includes(userId)) {
throw new Error('User is not available for this manager.'); throw new Error('User is not available for this manager.');
} }
} }
@@ -594,6 +506,14 @@ export const resolvers = {
const users = await context.prisma.user.findMany({ const users = await context.prisma.user.findMany({
where: managedUsersWhere, where: managedUsersWhere,
include: { include: {
messengerConnections: {
where: {
type: 'TELEGRAM',
isActive: true,
},
orderBy: { createdAt: 'desc' },
take: 1,
},
counterpartyProfile: { counterpartyProfile: {
select: { select: {
companyName: true, companyName: true,
@@ -626,6 +546,7 @@ export const resolvers = {
createdAt: user.createdAt, createdAt: user.createdAt,
orderCount: user._count.clientOrders, orderCount: user._count.clientOrders,
lastOrderAt: user.clientOrders[0]?.createdAt ?? null, lastOrderAt: user.clientOrders[0]?.createdAt ?? null,
telegramConnection: user.messengerConnections[0] ?? null,
})); }));
}, },

View File

@@ -147,6 +147,7 @@ type ManagerUser {
createdAt: DateTime! createdAt: DateTime!
orderCount: Int! orderCount: Int!
lastOrderAt: DateTime lastOrderAt: DateTime
telegramConnection: MessengerConnection
} }
type MessengerConnection { type MessengerConnection {

View File

@@ -18,6 +18,7 @@ import {
issueTemporaryLoginToken, issueTemporaryLoginToken,
verifyAccessToken, verifyAccessToken,
} from './auth.js'; } from './auth.js';
import { canManagerAccessUser, isManagerRole } from './access.js';
import { buildContext } from './context.js'; import { buildContext } from './context.js';
import { sendMessengerMessage } from './messenger.js'; import { sendMessengerMessage } from './messenger.js';
import { prisma } from './prisma-client.js'; import { prisma } from './prisma-client.js';
@@ -258,12 +259,29 @@ app.get('/messenger/avatar/:connectionId', async (req, res) => {
const connection = await prisma.messengerConnection.findFirst({ const connection = await prisma.messengerConnection.findFirst({
where: { where: {
id: connectionId, id: connectionId,
userId: user.id,
type: 'TELEGRAM', type: 'TELEGRAM',
isActive: true, isActive: true,
}, },
}); });
if (!connection) {
res.status(404).json({ error: 'Telegram avatar not found.' });
return;
}
if (connection.userId !== user.id) {
if (!isManagerRole(user.role)) {
res.status(403).json({ error: 'Access denied.' });
return;
}
const canAccess = await canManagerAccessUser(prisma, user, connection.userId);
if (!canAccess) {
res.status(403).json({ error: 'Access denied.' });
return;
}
}
if (!connection?.avatarFileId) { if (!connection?.avatarFileId) {
res.status(404).json({ error: 'Telegram avatar not found.' }); res.status(404).json({ error: 'Telegram avatar not found.' });
return; return;