Expose manager telegram avatars
This commit is contained in:
99
src/access.js
Normal file
99
src/access.js
Normal 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);
|
||||||
|
}
|
||||||
113
src/resolvers.js
113
src/resolvers.js
@@ -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,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ type ManagerUser {
|
|||||||
createdAt: DateTime!
|
createdAt: DateTime!
|
||||||
orderCount: Int!
|
orderCount: Int!
|
||||||
lastOrderAt: DateTime
|
lastOrderAt: DateTime
|
||||||
|
telegramConnection: MessengerConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessengerConnection {
|
type MessengerConnection {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user