mirror of https://github.com/buster-so/buster.git
Merge remote-tracking branch 'origin/staging' into staging
This commit is contained in:
commit
cb445e32a8
|
@ -1,8 +1,10 @@
|
|||
import { Hono } from 'hono';
|
||||
import GET from './GET';
|
||||
import SHARING from './sharing';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.route('/', GET);
|
||||
app.route('/sharing', SHARING);
|
||||
|
||||
export default app;
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import { findUsersByEmails, getChatById, removeAssetPermission } from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareDeleteRequest, ShareDeleteResponse } from '@buster/server-shared/share';
|
||||
import { ShareDeleteRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function deleteChatSharingHandler(
|
||||
chatId: string,
|
||||
emails: ShareDeleteRequest,
|
||||
user: User
|
||||
): Promise<ShareDeleteResponse> {
|
||||
// Get the chat to verify it exists and get owner info
|
||||
const chat = await getChatById(chatId);
|
||||
if (!chat) {
|
||||
throw new HTTPException(404, { message: 'Chat not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: chatId,
|
||||
assetType: 'chat',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: chat.workspaceSharing,
|
||||
organizationId: chat.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to delete sharing for this chat',
|
||||
});
|
||||
}
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const removedEmails = [];
|
||||
const notFoundEmails = [];
|
||||
|
||||
for (const email of emails) {
|
||||
const targetUser = userMap.get(email);
|
||||
|
||||
if (!targetUser) {
|
||||
notFoundEmails.push(email);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't allow removing permissions from the owner
|
||||
if (targetUser.id === chat.createdBy) {
|
||||
continue; // Skip the owner
|
||||
}
|
||||
|
||||
// Remove the permission
|
||||
await removeAssetPermission({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user',
|
||||
assetId: chatId,
|
||||
assetType: 'chat',
|
||||
updatedBy: user.id,
|
||||
});
|
||||
|
||||
removedEmails.push(email);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
removed: removedEmails,
|
||||
notFound: notFoundEmails,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().delete('/', zValidator('json', ShareDeleteRequestSchema), async (c) => {
|
||||
const chatId = c.req.param('id');
|
||||
const emails = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!chatId) {
|
||||
throw new HTTPException(400, { message: 'Chat ID is required' });
|
||||
}
|
||||
|
||||
const result = await deleteChatSharingHandler(chatId, emails, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,55 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import { checkAssetPermission, getChatById, listAssetPermissions } from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareGetResponse } from '@buster/server-shared/reports';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function getChatSharingHandler(chatId: string, user: User): Promise<ShareGetResponse> {
|
||||
// Check if chat exists
|
||||
const chat = await getChatById(chatId);
|
||||
if (!chat) {
|
||||
throw new HTTPException(404, { message: 'Chat not found' });
|
||||
}
|
||||
|
||||
// Check if user has permission to view the chat
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: chatId,
|
||||
assetType: 'chat',
|
||||
requiredRole: 'can_view',
|
||||
workspaceSharing: chat.workspaceSharing,
|
||||
organizationId: chat.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to view this chat',
|
||||
});
|
||||
}
|
||||
|
||||
// Get all permissions for the chat
|
||||
const permissions = await listAssetPermissions({
|
||||
assetId: chatId,
|
||||
assetType: 'chat',
|
||||
});
|
||||
|
||||
return {
|
||||
permissions,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().get('/', async (c) => {
|
||||
const chatId = c.req.param('id');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!chatId) {
|
||||
throw new HTTPException(400, { message: 'Chat ID is required' });
|
||||
}
|
||||
|
||||
const result = await getChatSharingHandler(chatId, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,113 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
findUsersByEmails,
|
||||
getChatById,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { SharePostRequest, SharePostResponse } from '@buster/server-shared/share';
|
||||
import { SharePostRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function createChatSharingHandler(
|
||||
chatId: string,
|
||||
shareRequests: SharePostRequest,
|
||||
user: User
|
||||
): Promise<SharePostResponse> {
|
||||
// Get the chat to verify it exists
|
||||
const chat = await getChatById(chatId);
|
||||
if (!chat) {
|
||||
throw new HTTPException(404, { message: 'Chat not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: chatId,
|
||||
assetType: 'chat',
|
||||
requiredRole: 'can_edit',
|
||||
workspaceSharing: chat.workspaceSharing,
|
||||
organizationId: chat.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to edit this chat',
|
||||
});
|
||||
}
|
||||
|
||||
// Extract emails from the share requests
|
||||
const emails = shareRequests.map((req) => req.email);
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const permissions = [];
|
||||
const sharedEmails = [];
|
||||
const notFoundEmails = [];
|
||||
|
||||
for (const shareRequest of shareRequests) {
|
||||
const targetUser = userMap.get(shareRequest.email);
|
||||
|
||||
if (!targetUser) {
|
||||
notFoundEmails.push(shareRequest.email);
|
||||
continue;
|
||||
}
|
||||
|
||||
sharedEmails.push(shareRequest.email);
|
||||
|
||||
// Map ShareRole to AssetPermissionRole
|
||||
const roleMapping = {
|
||||
owner: 'owner',
|
||||
full_access: 'full_access',
|
||||
can_edit: 'can_edit',
|
||||
can_filter: 'can_filter',
|
||||
can_view: 'can_view',
|
||||
viewer: 'can_view', // Map viewer to can_view
|
||||
} as const;
|
||||
|
||||
const mappedRole = roleMapping[shareRequest.role];
|
||||
if (!mappedRole) {
|
||||
throw new HTTPException(400, {
|
||||
message: `Invalid role: ${shareRequest.role} for user ${shareRequest.email}`,
|
||||
});
|
||||
}
|
||||
|
||||
permissions.push({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user' as const,
|
||||
assetId: chatId,
|
||||
assetType: 'chat' as const,
|
||||
role: mappedRole,
|
||||
createdBy: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create permissions in bulk
|
||||
if (permissions.length > 0) {
|
||||
await bulkCreateAssetPermissions({ permissions });
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
shared: sharedEmails,
|
||||
notFound: notFoundEmails,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().post('/', zValidator('json', SharePostRequestSchema), async (c) => {
|
||||
const chatId = c.req.param('id');
|
||||
const shareRequests = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!chatId) {
|
||||
throw new HTTPException(400, { message: 'Chat ID is required' });
|
||||
}
|
||||
|
||||
const result = await createChatSharingHandler(chatId, shareRequests, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,140 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
findUsersByEmails,
|
||||
getChatById,
|
||||
getUserOrganizationId,
|
||||
updateChatSharing,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { GetChatResponse } from '@buster/server-shared/chats';
|
||||
import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { getChatHandler } from '../GET';
|
||||
|
||||
export async function updateChatShareHandler(
|
||||
chatId: string,
|
||||
request: ShareUpdateRequest,
|
||||
user: User & { organizationId: string }
|
||||
) {
|
||||
// Check if chat exists
|
||||
const chat = await getChatById(chatId);
|
||||
if (!chat) {
|
||||
throw new HTTPException(404, { message: 'Chat not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: chatId,
|
||||
assetType: 'chat',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: chat.workspaceSharing,
|
||||
organizationId: chat.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to update sharing for this chat',
|
||||
});
|
||||
}
|
||||
|
||||
const { publicly_accessible, public_expiry_date, workspace_sharing, users } = request;
|
||||
|
||||
// Handle user permissions if provided
|
||||
if (users && users.length > 0) {
|
||||
// Extract emails from the user permissions
|
||||
const emails = users.map((u) => u.email);
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const permissions = [];
|
||||
|
||||
for (const userPermission of users) {
|
||||
const targetUser = userMap.get(userPermission.email);
|
||||
|
||||
if (!targetUser) {
|
||||
// Skip users that don't exist - you may want to collect these and return as warnings
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map ShareRole to AssetPermissionRole
|
||||
const roleMapping = {
|
||||
owner: 'owner',
|
||||
full_access: 'full_access',
|
||||
can_edit: 'can_edit',
|
||||
can_filter: 'can_filter',
|
||||
can_view: 'can_view',
|
||||
viewer: 'can_view', // Map viewer to can_view
|
||||
} as const;
|
||||
|
||||
const mappedRole = roleMapping[userPermission.role];
|
||||
if (!mappedRole) {
|
||||
throw new HTTPException(400, {
|
||||
message: `Invalid role: ${userPermission.role} for user ${userPermission.email}`,
|
||||
});
|
||||
}
|
||||
|
||||
permissions.push({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user' as const,
|
||||
assetId: chatId,
|
||||
assetType: 'chat' as const,
|
||||
role: mappedRole,
|
||||
createdBy: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create/update permissions in bulk
|
||||
if (permissions.length > 0) {
|
||||
await bulkCreateAssetPermissions({ permissions });
|
||||
}
|
||||
}
|
||||
|
||||
// Update chat sharing settings - only pass defined values
|
||||
const updateOptions: Parameters<typeof updateChatSharing>[2] = {};
|
||||
if (publicly_accessible !== undefined) {
|
||||
updateOptions.publicly_accessible = publicly_accessible;
|
||||
}
|
||||
if (public_expiry_date !== undefined) {
|
||||
updateOptions.public_expiry_date = public_expiry_date;
|
||||
}
|
||||
if (workspace_sharing !== undefined) {
|
||||
updateOptions.workspace_sharing = workspace_sharing;
|
||||
}
|
||||
await updateChatSharing(chatId, user.id, updateOptions);
|
||||
|
||||
const updatedChat: GetChatResponse = await getChatHandler({
|
||||
chatId,
|
||||
user,
|
||||
});
|
||||
|
||||
return updatedChat;
|
||||
}
|
||||
|
||||
const app = new Hono().put('/', zValidator('json', ShareUpdateRequestSchema), async (c) => {
|
||||
const chatId = c.req.param('id');
|
||||
const request = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!chatId) {
|
||||
throw new HTTPException(404, { message: 'Chat not found' });
|
||||
}
|
||||
|
||||
const userOrg = await getUserOrganizationId(user.id);
|
||||
|
||||
if (!userOrg) {
|
||||
throw new HTTPException(403, { message: 'User is not associated with an organization' });
|
||||
}
|
||||
|
||||
const updatedChat: GetChatResponse = await updateChatShareHandler(chatId, request, {
|
||||
...user,
|
||||
organizationId: userOrg.organizationId,
|
||||
});
|
||||
|
||||
return c.json(updatedChat);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,15 @@
|
|||
import { Hono } from 'hono';
|
||||
import { requireAuth } from '../../../../../middleware/auth';
|
||||
import DELETE from './DELETE';
|
||||
import GET from './GET';
|
||||
import POST from './POST';
|
||||
import PUT from './PUT';
|
||||
|
||||
const app = new Hono()
|
||||
.use('*', requireAuth)
|
||||
.route('/', GET)
|
||||
.route('/', POST)
|
||||
.route('/', PUT)
|
||||
.route('/', DELETE);
|
||||
|
||||
export default app;
|
|
@ -1,9 +1,11 @@
|
|||
import { Hono } from 'hono';
|
||||
import '../../../../types/hono.types';
|
||||
import dashboardByIdRoutes from './GET';
|
||||
import SHARING from './sharing';
|
||||
|
||||
const app = new Hono()
|
||||
// /dashboards/:id GET
|
||||
.route('/', dashboardByIdRoutes);
|
||||
.route('/', dashboardByIdRoutes)
|
||||
.route('/sharing', SHARING);
|
||||
|
||||
export default app;
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
findUsersByEmails,
|
||||
getDashboardById,
|
||||
removeAssetPermission,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareDeleteResponse } from '@buster/server-shared/share';
|
||||
import type { ShareDeleteRequest } from '@buster/server-shared/share';
|
||||
import { ShareDeleteRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function deleteDashboardSharingHandler(
|
||||
dashboardId: string,
|
||||
emails: ShareDeleteRequest,
|
||||
user: User
|
||||
): Promise<ShareDeleteResponse> {
|
||||
// Get the dashboard to verify it exists and get owner info
|
||||
const dashboard = await getDashboardById({ dashboardId });
|
||||
if (!dashboard) {
|
||||
throw new HTTPException(404, { message: 'Dashboard not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: dashboard.workspaceSharing,
|
||||
organizationId: dashboard.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to delete sharing for this dashboard',
|
||||
});
|
||||
}
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const removedEmails = [];
|
||||
const notFoundEmails = [];
|
||||
|
||||
for (const email of emails) {
|
||||
const targetUser = userMap.get(email);
|
||||
|
||||
if (!targetUser) {
|
||||
notFoundEmails.push(email);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't allow removing permissions from the owner
|
||||
if (targetUser.id === dashboard.createdBy) {
|
||||
continue; // Skip the owner
|
||||
}
|
||||
|
||||
// Remove the permission
|
||||
await removeAssetPermission({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user',
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file',
|
||||
updatedBy: user.id,
|
||||
});
|
||||
|
||||
removedEmails.push(email);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
removed: removedEmails,
|
||||
notFound: notFoundEmails,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().delete('/', zValidator('json', ShareDeleteRequestSchema), async (c) => {
|
||||
const dashboardId = c.req.param('id');
|
||||
const emails = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!dashboardId) {
|
||||
throw new HTTPException(400, { message: 'Dashboard ID is required' });
|
||||
}
|
||||
|
||||
const result = await deleteDashboardSharingHandler(dashboardId, emails, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,58 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import { getDashboardById, listAssetPermissions } from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareGetResponse } from '@buster/server-shared/reports';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function getDashboardSharingHandler(
|
||||
dashboardId: string,
|
||||
user: User
|
||||
): Promise<ShareGetResponse> {
|
||||
// Check if dashboard exists
|
||||
const dashboard = await getDashboardById({ dashboardId });
|
||||
if (!dashboard) {
|
||||
throw new HTTPException(404, { message: 'Dashboard not found' });
|
||||
}
|
||||
|
||||
// Check if user has permission to view the dashboard
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file',
|
||||
requiredRole: 'can_view',
|
||||
workspaceSharing: dashboard.workspaceSharing,
|
||||
organizationId: dashboard.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to view this dashboard',
|
||||
});
|
||||
}
|
||||
|
||||
// Get all permissions for the dashboard
|
||||
const permissions = await listAssetPermissions({
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file',
|
||||
});
|
||||
|
||||
return {
|
||||
permissions,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().get('/', async (c) => {
|
||||
const dashboardId = c.req.param('id');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!dashboardId) {
|
||||
throw new HTTPException(400, { message: 'Dashboard ID is required' });
|
||||
}
|
||||
|
||||
const result = await getDashboardSharingHandler(dashboardId, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,114 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
findUsersByEmails,
|
||||
getDashboardById,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { SharePostResponse } from '@buster/server-shared/share';
|
||||
import type { SharePostRequest } from '@buster/server-shared/share';
|
||||
import { SharePostRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function createDashboardSharingHandler(
|
||||
dashboardId: string,
|
||||
shareRequests: SharePostRequest,
|
||||
user: User
|
||||
): Promise<SharePostResponse> {
|
||||
// Get the dashboard to verify it exists
|
||||
const dashboard = await getDashboardById({ dashboardId });
|
||||
if (!dashboard) {
|
||||
throw new HTTPException(404, { message: 'Dashboard not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file',
|
||||
requiredRole: 'can_edit',
|
||||
workspaceSharing: dashboard.workspaceSharing,
|
||||
organizationId: dashboard.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to edit this dashboard',
|
||||
});
|
||||
}
|
||||
|
||||
// Extract emails from the share requests
|
||||
const emails = shareRequests.map((req) => req.email);
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const permissions = [];
|
||||
const sharedEmails = [];
|
||||
const notFoundEmails = [];
|
||||
|
||||
for (const shareRequest of shareRequests) {
|
||||
const targetUser = userMap.get(shareRequest.email);
|
||||
|
||||
if (!targetUser) {
|
||||
notFoundEmails.push(shareRequest.email);
|
||||
continue;
|
||||
}
|
||||
|
||||
sharedEmails.push(shareRequest.email);
|
||||
|
||||
// Map ShareRole to AssetPermissionRole
|
||||
const roleMapping = {
|
||||
owner: 'owner',
|
||||
full_access: 'full_access',
|
||||
can_edit: 'can_edit',
|
||||
can_filter: 'can_filter',
|
||||
can_view: 'can_view',
|
||||
viewer: 'can_view', // Map viewer to can_view
|
||||
} as const;
|
||||
|
||||
const mappedRole = roleMapping[shareRequest.role];
|
||||
if (!mappedRole) {
|
||||
throw new HTTPException(400, {
|
||||
message: `Invalid role: ${shareRequest.role} for user ${shareRequest.email}`,
|
||||
});
|
||||
}
|
||||
|
||||
permissions.push({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user' as const,
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file' as const,
|
||||
role: mappedRole,
|
||||
createdBy: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create permissions in bulk
|
||||
if (permissions.length > 0) {
|
||||
await bulkCreateAssetPermissions({ permissions });
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
shared: sharedEmails,
|
||||
notFound: notFoundEmails,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().post('/', zValidator('json', SharePostRequestSchema), async (c) => {
|
||||
const dashboardId = c.req.param('id');
|
||||
const shareRequests = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!dashboardId) {
|
||||
throw new HTTPException(400, { message: 'Dashboard ID is required' });
|
||||
}
|
||||
|
||||
const result = await createDashboardSharingHandler(dashboardId, shareRequests, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,139 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
findUsersByEmails,
|
||||
getDashboardById,
|
||||
getUserOrganizationId,
|
||||
updateDashboard,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { GetDashboardResponse } from '@buster/server-shared/dashboards';
|
||||
import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { getDashboardHandler } from '../GET';
|
||||
|
||||
export async function updateDashboardShareHandler(
|
||||
dashboardId: string,
|
||||
request: ShareUpdateRequest,
|
||||
user: User & { organizationId: string }
|
||||
) {
|
||||
// Check if dashboard exists
|
||||
const dashboard = await getDashboardById({ dashboardId });
|
||||
if (!dashboard) {
|
||||
throw new HTTPException(404, { message: 'Dashboard not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: dashboard.workspaceSharing,
|
||||
organizationId: dashboard.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to update sharing for this dashboard',
|
||||
});
|
||||
}
|
||||
|
||||
const { publicly_accessible, public_expiry_date, public_password, workspace_sharing, users } =
|
||||
request;
|
||||
|
||||
// Handle user permissions if provided
|
||||
if (users && users.length > 0) {
|
||||
// Extract emails from the user permissions
|
||||
const emails = users.map((u) => u.email);
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const permissions = [];
|
||||
|
||||
for (const userPermission of users) {
|
||||
const targetUser = userMap.get(userPermission.email);
|
||||
|
||||
if (!targetUser) {
|
||||
// Skip users that don't exist - you may want to collect these and return as warnings
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map ShareRole to AssetPermissionRole
|
||||
const roleMapping = {
|
||||
owner: 'owner',
|
||||
full_access: 'full_access',
|
||||
can_edit: 'can_edit',
|
||||
can_filter: 'can_filter',
|
||||
can_view: 'can_view',
|
||||
viewer: 'can_view', // Map viewer to can_view
|
||||
} as const;
|
||||
|
||||
const mappedRole = roleMapping[userPermission.role];
|
||||
if (!mappedRole) {
|
||||
throw new HTTPException(400, {
|
||||
message: `Invalid role: ${userPermission.role} for user ${userPermission.email}`,
|
||||
});
|
||||
}
|
||||
|
||||
permissions.push({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user' as const,
|
||||
assetId: dashboardId,
|
||||
assetType: 'dashboard_file' as const,
|
||||
role: mappedRole,
|
||||
createdBy: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create/update permissions in bulk
|
||||
if (permissions.length > 0) {
|
||||
await bulkCreateAssetPermissions({ permissions });
|
||||
}
|
||||
}
|
||||
|
||||
// Update dashboard sharing settings
|
||||
await updateDashboard({
|
||||
dashboardId,
|
||||
userId: user.id,
|
||||
publicly_accessible,
|
||||
public_expiry_date,
|
||||
public_password,
|
||||
workspace_sharing,
|
||||
});
|
||||
|
||||
const updatedDashboard: GetDashboardResponse = await getDashboardHandler({ dashboardId }, user);
|
||||
|
||||
return updatedDashboard;
|
||||
}
|
||||
|
||||
const app = new Hono().put('/', zValidator('json', ShareUpdateRequestSchema), async (c) => {
|
||||
const dashboardId = c.req.param('id');
|
||||
const request = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!dashboardId) {
|
||||
throw new HTTPException(404, { message: 'Dashboard not found' });
|
||||
}
|
||||
|
||||
const userOrg = await getUserOrganizationId(user.id);
|
||||
|
||||
if (!userOrg) {
|
||||
throw new HTTPException(403, { message: 'User is not associated with an organization' });
|
||||
}
|
||||
|
||||
const updatedDashboard: GetDashboardResponse = await updateDashboardShareHandler(
|
||||
dashboardId,
|
||||
request,
|
||||
{
|
||||
...user,
|
||||
organizationId: userOrg.organizationId,
|
||||
}
|
||||
);
|
||||
|
||||
return c.json(updatedDashboard);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,15 @@
|
|||
import { Hono } from 'hono';
|
||||
import { requireAuth } from '../../../../../middleware/auth';
|
||||
import DELETE from './DELETE';
|
||||
import GET from './GET';
|
||||
import POST from './POST';
|
||||
import PUT from './PUT';
|
||||
|
||||
const app = new Hono()
|
||||
.use('*', requireAuth)
|
||||
.route('/', GET)
|
||||
.route('/', POST)
|
||||
.route('/', PUT)
|
||||
.route('/', DELETE);
|
||||
|
||||
export default app;
|
|
@ -3,11 +3,13 @@ import { standardErrorHandler } from '../../../../utils/response';
|
|||
import GET from './GET';
|
||||
import DATA from './data/GET';
|
||||
import DOWNLOAD from './download/GET';
|
||||
import SHARING from './sharing';
|
||||
|
||||
const app = new Hono()
|
||||
.route('/', GET)
|
||||
.route('/data', DATA)
|
||||
.route('/download', DOWNLOAD)
|
||||
.route('/sharing', SHARING)
|
||||
.onError(standardErrorHandler);
|
||||
|
||||
export default app;
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
findUsersByEmails,
|
||||
getMetricFileById,
|
||||
removeAssetPermission,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareDeleteResponse } from '@buster/server-shared/share';
|
||||
import type { ShareDeleteRequest } from '@buster/server-shared/share';
|
||||
import { ShareDeleteRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function deleteMetricSharingHandler(
|
||||
metricId: string,
|
||||
emails: ShareDeleteRequest,
|
||||
user: User
|
||||
): Promise<ShareDeleteResponse> {
|
||||
// Get the metric to verify it exists and get owner info
|
||||
const metric = await getMetricFileById(metricId);
|
||||
if (!metric) {
|
||||
throw new HTTPException(404, { message: 'Metric not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: metric.workspaceSharing,
|
||||
organizationId: metric.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to delete sharing for this metric',
|
||||
});
|
||||
}
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const removedEmails = [];
|
||||
const notFoundEmails = [];
|
||||
|
||||
for (const email of emails) {
|
||||
const targetUser = userMap.get(email);
|
||||
|
||||
if (!targetUser) {
|
||||
notFoundEmails.push(email);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't allow removing permissions from the owner
|
||||
if (targetUser.id === metric.createdBy) {
|
||||
continue; // Skip the owner
|
||||
}
|
||||
|
||||
// Remove the permission
|
||||
await removeAssetPermission({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user',
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file',
|
||||
updatedBy: user.id,
|
||||
});
|
||||
|
||||
removedEmails.push(email);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
removed: removedEmails,
|
||||
notFound: notFoundEmails,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().delete('/', zValidator('json', ShareDeleteRequestSchema), async (c) => {
|
||||
const metricId = c.req.param('id');
|
||||
const emails = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!metricId) {
|
||||
throw new HTTPException(400, { message: 'Metric ID is required' });
|
||||
}
|
||||
|
||||
const result = await deleteMetricSharingHandler(metricId, emails, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,58 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import { getMetricFileById, listAssetPermissions } from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareGetResponse } from '@buster/server-shared/reports';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export async function getMetricSharingHandler(
|
||||
metricId: string,
|
||||
user: User
|
||||
): Promise<ShareGetResponse> {
|
||||
// Check if metric exists
|
||||
const metric = await getMetricFileById(metricId);
|
||||
if (!metric) {
|
||||
throw new HTTPException(404, { message: 'Metric not found' });
|
||||
}
|
||||
|
||||
// Check if user has permission to view the metric
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file',
|
||||
requiredRole: 'can_view',
|
||||
workspaceSharing: metric.workspaceSharing,
|
||||
organizationId: metric.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to view this metric',
|
||||
});
|
||||
}
|
||||
|
||||
// Get all permissions for the metric
|
||||
const permissions = await listAssetPermissions({
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file',
|
||||
});
|
||||
|
||||
return {
|
||||
permissions,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().get('/', async (c) => {
|
||||
const metricId = c.req.param('id');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!metricId) {
|
||||
throw new HTTPException(400, { message: 'Metric ID is required' });
|
||||
}
|
||||
|
||||
const result = await getMetricSharingHandler(metricId, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,116 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
findUsersByEmails,
|
||||
getMetricFileById,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { SharePostResponse } from '@buster/server-shared/share';
|
||||
import type { SharePostRequest } from '@buster/server-shared/share';
|
||||
import { SharePostRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { checkIfAssetIsEditable } from '../../../../../shared-helpers/asset-public-access';
|
||||
|
||||
export async function createMetricSharingHandler(
|
||||
metricId: string,
|
||||
shareRequests: SharePostRequest,
|
||||
user: User
|
||||
): Promise<SharePostResponse> {
|
||||
// Get the metric to verify it exists
|
||||
const metric = await getMetricFileById(metricId);
|
||||
if (!metric) {
|
||||
throw new HTTPException(404, { message: 'Metric not found' });
|
||||
}
|
||||
|
||||
// Check if user has permission to edit the metric
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file',
|
||||
requiredRole: 'can_edit',
|
||||
workspaceSharing: metric.workspaceSharing,
|
||||
organizationId: metric.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to edit this metric',
|
||||
});
|
||||
}
|
||||
|
||||
// Extract emails from the share requests
|
||||
const emails = shareRequests.map((req) => req.email);
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const permissions = [];
|
||||
const sharedEmails = [];
|
||||
const notFoundEmails = [];
|
||||
|
||||
for (const shareRequest of shareRequests) {
|
||||
const targetUser = userMap.get(shareRequest.email);
|
||||
|
||||
if (!targetUser) {
|
||||
notFoundEmails.push(shareRequest.email);
|
||||
continue;
|
||||
}
|
||||
|
||||
sharedEmails.push(shareRequest.email);
|
||||
|
||||
// Map ShareRole to AssetPermissionRole
|
||||
const roleMapping = {
|
||||
owner: 'owner',
|
||||
full_access: 'full_access',
|
||||
can_edit: 'can_edit',
|
||||
can_filter: 'can_filter',
|
||||
can_view: 'can_view',
|
||||
viewer: 'can_view', // Map viewer to can_view
|
||||
} as const;
|
||||
|
||||
const mappedRole = roleMapping[shareRequest.role];
|
||||
if (!mappedRole) {
|
||||
throw new HTTPException(400, {
|
||||
message: `Invalid role: ${shareRequest.role} for user ${shareRequest.email}`,
|
||||
});
|
||||
}
|
||||
|
||||
permissions.push({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user' as const,
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file' as const,
|
||||
role: mappedRole,
|
||||
createdBy: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create permissions in bulk
|
||||
if (permissions.length > 0) {
|
||||
await bulkCreateAssetPermissions({ permissions });
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
shared: sharedEmails,
|
||||
notFound: notFoundEmails,
|
||||
};
|
||||
}
|
||||
|
||||
const app = new Hono().post('/', zValidator('json', SharePostRequestSchema), async (c) => {
|
||||
const metricId = c.req.param('id');
|
||||
const shareRequests = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!metricId) {
|
||||
throw new HTTPException(400, { message: 'Metric ID is required' });
|
||||
}
|
||||
|
||||
const result = await createMetricSharingHandler(metricId, shareRequests, user);
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,139 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
findUsersByEmails,
|
||||
getMetricFileById,
|
||||
getUserOrganizationId,
|
||||
updateMetric,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareMetricUpdateResponse } from '@buster/server-shared/metrics';
|
||||
import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { getMetricHandler } from '../GET';
|
||||
|
||||
export async function updateMetricShareHandler(
|
||||
metricId: string,
|
||||
request: ShareUpdateRequest,
|
||||
user: User & { organizationId: string }
|
||||
) {
|
||||
// Check if metric exists
|
||||
const metric = await getMetricFileById(metricId);
|
||||
if (!metric) {
|
||||
throw new HTTPException(404, { message: 'Metric not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: metric.workspaceSharing,
|
||||
organizationId: metric.organizationId,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to update sharing for this metric',
|
||||
});
|
||||
}
|
||||
|
||||
const { publicly_accessible, public_expiry_date, public_password, workspace_sharing, users } =
|
||||
request;
|
||||
|
||||
// Handle user permissions if provided
|
||||
if (users && users.length > 0) {
|
||||
// Extract emails from the user permissions
|
||||
const emails = users.map((u) => u.email);
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
const permissions = [];
|
||||
|
||||
for (const userPermission of users) {
|
||||
const targetUser = userMap.get(userPermission.email);
|
||||
|
||||
if (!targetUser) {
|
||||
// Skip users that don't exist - you may want to collect these and return as warnings
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map ShareRole to AssetPermissionRole
|
||||
const roleMapping = {
|
||||
owner: 'owner',
|
||||
full_access: 'full_access',
|
||||
can_edit: 'can_edit',
|
||||
can_filter: 'can_filter',
|
||||
can_view: 'can_view',
|
||||
viewer: 'can_view', // Map viewer to can_view
|
||||
} as const;
|
||||
|
||||
const mappedRole = roleMapping[userPermission.role];
|
||||
if (!mappedRole) {
|
||||
throw new HTTPException(400, {
|
||||
message: `Invalid role: ${userPermission.role} for user ${userPermission.email}`,
|
||||
});
|
||||
}
|
||||
|
||||
permissions.push({
|
||||
identityId: targetUser.id,
|
||||
identityType: 'user' as const,
|
||||
assetId: metricId,
|
||||
assetType: 'metric_file' as const,
|
||||
role: mappedRole,
|
||||
createdBy: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Create/update permissions in bulk
|
||||
if (permissions.length > 0) {
|
||||
await bulkCreateAssetPermissions({ permissions });
|
||||
}
|
||||
}
|
||||
|
||||
// Update metric sharing settings
|
||||
await updateMetric({
|
||||
metricId,
|
||||
userId: user.id,
|
||||
publicly_accessible,
|
||||
public_expiry_date,
|
||||
public_password,
|
||||
workspace_sharing,
|
||||
});
|
||||
|
||||
const updatedMetric: ShareMetricUpdateResponse = await getMetricHandler({ metricId }, user);
|
||||
|
||||
return updatedMetric;
|
||||
}
|
||||
|
||||
const app = new Hono().put('/', zValidator('json', ShareUpdateRequestSchema), async (c) => {
|
||||
const metricId = c.req.param('id');
|
||||
const request = c.req.valid('json');
|
||||
const user = c.get('busterUser');
|
||||
|
||||
if (!metricId) {
|
||||
throw new HTTPException(404, { message: 'Metric not found' });
|
||||
}
|
||||
|
||||
const userOrg = await getUserOrganizationId(user.id);
|
||||
|
||||
if (!userOrg) {
|
||||
throw new HTTPException(403, { message: 'User is not associated with an organization' });
|
||||
}
|
||||
|
||||
const updatedMetric: ShareMetricUpdateResponse = await updateMetricShareHandler(
|
||||
metricId,
|
||||
request,
|
||||
{
|
||||
...user,
|
||||
organizationId: userOrg.organizationId,
|
||||
}
|
||||
);
|
||||
|
||||
return c.json(updatedMetric);
|
||||
});
|
||||
|
||||
export default app;
|
|
@ -0,0 +1,15 @@
|
|||
import { Hono } from 'hono';
|
||||
import { requireAuth } from '../../../../../middleware/auth';
|
||||
import DELETE from './DELETE';
|
||||
import GET from './GET';
|
||||
import POST from './POST';
|
||||
import PUT from './PUT';
|
||||
|
||||
const app = new Hono()
|
||||
.use('*', requireAuth)
|
||||
.route('/', GET)
|
||||
.route('/', POST)
|
||||
.route('/', PUT)
|
||||
.route('/', DELETE);
|
||||
|
||||
export default app;
|
|
@ -1,37 +1,43 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
findUsersByEmails,
|
||||
getReportFileById,
|
||||
getReportWorkspaceSharing,
|
||||
removeAssetPermission,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { ShareDeleteResponse } from '@buster/server-shared/reports';
|
||||
import type { ShareDeleteResponse } from '@buster/server-shared/share';
|
||||
import type { ShareDeleteRequest } from '@buster/server-shared/share';
|
||||
import { ShareDeleteRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { checkIfAssetIsEditable } from '../../../../../shared-helpers/asset-public-access';
|
||||
|
||||
export async function deleteReportSharingHandler(
|
||||
reportId: string,
|
||||
emails: ShareDeleteRequest,
|
||||
user: User
|
||||
): Promise<ShareDeleteResponse> {
|
||||
await checkIfAssetIsEditable({
|
||||
user,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
workspaceSharing: getReportWorkspaceSharing,
|
||||
requiredRole: 'full_access',
|
||||
});
|
||||
|
||||
// Get the report to verify it exists and get owner info
|
||||
const report = await getReportFileById({ reportId, userId: user.id });
|
||||
if (!report) {
|
||||
throw new HTTPException(404, { message: 'Report not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: report.workspace_sharing,
|
||||
organizationId: report.organization_id,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to delete sharing for this report',
|
||||
});
|
||||
}
|
||||
|
||||
// Find users by emails
|
||||
const userMap = await findUsersByEmails(emails);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
checkAssetPermission,
|
||||
getReportFileById,
|
||||
|
@ -19,10 +20,13 @@ export async function getReportSharingHandler(
|
|||
}
|
||||
|
||||
// Check if user has permission to view the report
|
||||
const permissionCheck = await checkAssetPermission({
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
userId: user.id,
|
||||
requiredRole: 'can_view',
|
||||
workspaceSharing: report.workspace_sharing,
|
||||
organizationId: report.organization_id,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
|
|
|
@ -1,37 +1,44 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
findUsersByEmails,
|
||||
getReportFileById,
|
||||
getReportWorkspaceSharing,
|
||||
} from '@buster/database/queries';
|
||||
import type { User } from '@buster/database/queries';
|
||||
import type { SharePostResponse } from '@buster/server-shared/reports';
|
||||
import type { SharePostResponse } from '@buster/server-shared/share';
|
||||
import type { SharePostRequest } from '@buster/server-shared/share';
|
||||
import { SharePostRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { checkIfAssetIsEditable } from '../../../../../shared-helpers/asset-public-access';
|
||||
|
||||
export async function createReportSharingHandler(
|
||||
reportId: string,
|
||||
shareRequests: SharePostRequest,
|
||||
user: User
|
||||
): Promise<SharePostResponse> {
|
||||
await checkIfAssetIsEditable({
|
||||
user,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
workspaceSharing: getReportWorkspaceSharing,
|
||||
requiredRole: 'can_edit',
|
||||
});
|
||||
|
||||
// Get the report to verify it exists
|
||||
const report = await getReportFileById({ reportId, userId: user.id });
|
||||
if (!report) {
|
||||
throw new HTTPException(404, { message: 'Report not found' });
|
||||
}
|
||||
|
||||
// Check if user has permission to edit the report
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
requiredRole: 'can_edit',
|
||||
workspaceSharing: report.workspace_sharing,
|
||||
organizationId: report.organization_id,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to edit this report',
|
||||
});
|
||||
}
|
||||
|
||||
// Extract emails from the share requests
|
||||
const emails = shareRequests.map((req) => req.email);
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
bulkCreateAssetPermissions,
|
||||
checkAssetPermission,
|
||||
findUsersByEmails,
|
||||
getReportFileById,
|
||||
getReportWorkspaceSharing,
|
||||
getUserOrganizationId,
|
||||
updateReport,
|
||||
} from '@buster/database/queries';
|
||||
|
@ -13,7 +12,6 @@ import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/serve
|
|||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { checkIfAssetIsEditable } from '../../../../../shared-helpers/asset-public-access';
|
||||
import { getReportHandler } from '../GET';
|
||||
|
||||
export async function updateReportShareHandler(
|
||||
|
@ -21,29 +19,27 @@ export async function updateReportShareHandler(
|
|||
request: ShareUpdateRequest,
|
||||
user: User & { organizationId: string }
|
||||
) {
|
||||
// Check if user has permission to edit asset permissions
|
||||
const permissionCheck = await checkAssetPermission({
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
// Check if user has at least full_access permission
|
||||
if (
|
||||
!permissionCheck.hasAccess ||
|
||||
(permissionCheck.role !== 'full_access' && permissionCheck.role !== 'owner')
|
||||
) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'User does not have permission to edit asset permissions',
|
||||
});
|
||||
}
|
||||
|
||||
// Check if report exists
|
||||
const report = await getReportFileById({ reportId, userId: user.id });
|
||||
if (!report) {
|
||||
throw new HTTPException(404, { message: 'Report not found' });
|
||||
}
|
||||
|
||||
const permissionCheck = await checkPermission({
|
||||
userId: user.id,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
requiredRole: 'full_access',
|
||||
workspaceSharing: report.workspace_sharing,
|
||||
organizationId: report.organization_id,
|
||||
});
|
||||
|
||||
if (!permissionCheck.hasAccess) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to update sharing for this report',
|
||||
});
|
||||
}
|
||||
|
||||
const { publicly_accessible, public_expiry_date, public_password, workspace_sharing, users } =
|
||||
request;
|
||||
|
||||
|
@ -131,15 +127,6 @@ const app = new Hono().put('/', zValidator('json', ShareUpdateRequestSchema), as
|
|||
throw new HTTPException(403, { message: 'User is not associated with an organization' });
|
||||
}
|
||||
|
||||
await checkIfAssetIsEditable({
|
||||
user,
|
||||
assetId: reportId,
|
||||
assetType: 'report_file',
|
||||
workspaceSharing: getReportWorkspaceSharing,
|
||||
organizationId: userOrg.organizationId,
|
||||
requiredRole: 'full_access',
|
||||
});
|
||||
|
||||
const updatedReport: ShareUpdateResponse = await updateReportShareHandler(reportId, request, {
|
||||
...user,
|
||||
organizationId: userOrg.organizationId,
|
||||
|
|
|
@ -14,7 +14,9 @@ import type {
|
|||
} from '@buster/server-shared/chats';
|
||||
import type {
|
||||
ShareDeleteRequest,
|
||||
ShareDeleteResponse,
|
||||
SharePostRequest,
|
||||
SharePostResponse,
|
||||
ShareUpdateRequest,
|
||||
} from '@buster/server-shared/share';
|
||||
import { mainApi, mainApiV2 } from '../instances';
|
||||
|
@ -61,11 +63,15 @@ export const duplicateChat = async ({
|
|||
};
|
||||
|
||||
export const shareChat = async ({ id, params }: { id: string; params: SharePostRequest }) => {
|
||||
return mainApi.post<string>(`${CHATS_BASE}/${id}/sharing`, params).then((res) => res.data);
|
||||
return mainApiV2
|
||||
.post<SharePostResponse>(`${CHATS_BASE}/${id}/sharing`, params)
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
export const unshareChat = async ({ id, data }: { id: string; data: ShareDeleteRequest }) => {
|
||||
return mainApi.delete<boolean>(`${CHATS_BASE}/${id}/sharing`, { data }).then((res) => res.data);
|
||||
return mainApiV2
|
||||
.delete<ShareDeleteResponse>(`${CHATS_BASE}/${id}/sharing`, { data })
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
export const updateChatShare = async ({
|
||||
|
@ -75,7 +81,7 @@ export const updateChatShare = async ({
|
|||
id: string;
|
||||
params: ShareUpdateRequest;
|
||||
}) => {
|
||||
return mainApi
|
||||
return mainApiV2
|
||||
.put<ShareChatResponse>(`${CHATS_BASE}/${id}/sharing`, params)
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import type { DashboardConfig, GetDashboardResponse } from '@buster/server-shared/dashboards';
|
||||
import type {
|
||||
ShareDeleteRequest,
|
||||
ShareDeleteResponse,
|
||||
SharePostRequest,
|
||||
SharePostResponse,
|
||||
ShareUpdateRequest,
|
||||
} from '@buster/server-shared/share';
|
||||
import type { BusterDashboardListItem } from '@/api/asset_interfaces/dashboard';
|
||||
|
@ -76,11 +78,15 @@ export const dashboardsDeleteDashboard = async (data: { ids: string[] }) => {
|
|||
// share dashboards
|
||||
|
||||
export const shareDashboard = async ({ id, params }: { id: string; params: SharePostRequest }) => {
|
||||
return mainApi.post<string>(`/dashboards/${id}/sharing`, params).then((res) => res.data);
|
||||
return mainApiV2
|
||||
.post<SharePostResponse>(`/dashboards/${id}/sharing`, params)
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
export const unshareDashboard = async ({ id, data }: { id: string; data: ShareDeleteRequest }) => {
|
||||
return mainApi.delete<string>(`/dashboards/${id}/sharing`, { data }).then((res) => res.data);
|
||||
return mainApiV2
|
||||
.delete<ShareDeleteResponse>(`/dashboards/${id}/sharing`, { data })
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
export const updateDashboardShare = async ({
|
||||
|
@ -90,7 +96,7 @@ export const updateDashboardShare = async ({
|
|||
id: string;
|
||||
params: ShareUpdateRequest;
|
||||
}) => {
|
||||
return mainApi
|
||||
return mainApiV2
|
||||
.put<GetDashboardResponse>(`/dashboards/${id}/sharing`, params)
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
|
|
@ -15,14 +15,15 @@ import type {
|
|||
MetricDownloadParams,
|
||||
MetricDownloadQueryParams,
|
||||
MetricDownloadResponse,
|
||||
ShareDeleteResponse,
|
||||
ShareUpdateResponse,
|
||||
ShareMetricUpdateResponse,
|
||||
UpdateMetricRequest,
|
||||
UpdateMetricResponse,
|
||||
} from '@buster/server-shared/metrics';
|
||||
import type {
|
||||
ShareDeleteRequest,
|
||||
ShareDeleteResponse,
|
||||
SharePostRequest,
|
||||
SharePostResponse,
|
||||
ShareUpdateRequest,
|
||||
} from '@buster/server-shared/share';
|
||||
import { mainApi, mainApiV2 } from '../instances';
|
||||
|
@ -80,11 +81,13 @@ export const bulkUpdateMetricVerificationStatus = async (
|
|||
// share metrics
|
||||
|
||||
export const shareMetric = async ({ id, params }: { id: string; params: SharePostRequest }) => {
|
||||
return mainApi.post<string>(`/metric_files/${id}/sharing`, params).then((res) => res.data);
|
||||
return mainApiV2
|
||||
.post<SharePostResponse>(`/metric_files/${id}/sharing`, params)
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
export const unshareMetric = async ({ id, data }: { id: string; data: ShareDeleteRequest }) => {
|
||||
return mainApi
|
||||
return mainApiV2
|
||||
.delete<ShareDeleteResponse>(`/metric_files/${id}/sharing`, { data })
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
@ -96,8 +99,8 @@ export const updateMetricShare = async ({
|
|||
id: string;
|
||||
params: ShareUpdateRequest;
|
||||
}) => {
|
||||
return mainApi
|
||||
.put<ShareUpdateResponse>(`/metric_files/${id}/sharing`, params)
|
||||
return mainApiV2
|
||||
.put<ShareMetricUpdateResponse>(`/metric_files/${id}/sharing`, params)
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
|
|
|
@ -282,13 +282,6 @@ export const useUnshareMetric = () => {
|
|||
});
|
||||
});
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
const upgradedMetric = upgradeMetricToIMetric(data, null);
|
||||
queryClient.setQueryData(
|
||||
metricsQueryKeys.metricsGetMetric(data.id, 'LATEST').queryKey,
|
||||
upgradedMetric
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,12 @@ import type {
|
|||
UpdateReportRequest,
|
||||
UpdateReportResponse,
|
||||
} from '@buster/server-shared/reports';
|
||||
import type { SharePostRequest, ShareUpdateRequest } from '@buster/server-shared/share';
|
||||
import type {
|
||||
ShareDeleteResponse,
|
||||
SharePostRequest,
|
||||
SharePostResponse,
|
||||
ShareUpdateRequest,
|
||||
} from '@buster/server-shared/share';
|
||||
import { mainApiV2 } from '../instances';
|
||||
|
||||
/**
|
||||
|
@ -42,7 +47,7 @@ export const updateReport = async ({
|
|||
*/
|
||||
export const shareReport = async ({ id, params }: { id: string; params: SharePostRequest }) => {
|
||||
return mainApiV2
|
||||
.post<GetReportResponse>(`/reports/${id}/sharing`, params)
|
||||
.post<SharePostResponse>(`/reports/${id}/sharing`, params)
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
|
@ -51,7 +56,7 @@ export const shareReport = async ({ id, params }: { id: string; params: SharePos
|
|||
*/
|
||||
export const unshareReport = async ({ id, data }: { id: string; data: string[] }) => {
|
||||
return mainApiV2
|
||||
.delete<GetReportResponse>(`/reports/${id}/sharing`, { data })
|
||||
.delete<ShareDeleteResponse>(`/reports/${id}/sharing`, { data })
|
||||
.then((res) => res.data);
|
||||
};
|
||||
|
||||
|
|
|
@ -223,3 +223,56 @@ export async function updateChat(
|
|||
throw new Error(`Failed to update chat fields for chat ${chatId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a chat's sharing settings
|
||||
*/
|
||||
export async function updateChatSharing(
|
||||
chatId: string,
|
||||
userId: string,
|
||||
options: {
|
||||
publicly_accessible?: boolean;
|
||||
public_expiry_date?: string | null;
|
||||
workspace_sharing?: 'none' | 'can_view' | 'can_edit' | 'full_access';
|
||||
}
|
||||
): Promise<{ success: boolean }> {
|
||||
const updateFields: UpdateableChatFields = {
|
||||
updatedBy: userId,
|
||||
};
|
||||
|
||||
if (options.publicly_accessible !== undefined) {
|
||||
updateFields.publiclyAccessible = options.publicly_accessible;
|
||||
updateFields.publiclyEnabledBy = options.publicly_accessible ? userId : null;
|
||||
}
|
||||
|
||||
if (options.public_expiry_date !== undefined) {
|
||||
updateFields.publicExpiryDate = options.public_expiry_date;
|
||||
}
|
||||
|
||||
if (options.workspace_sharing !== undefined) {
|
||||
updateFields.workspaceSharing = options.workspace_sharing;
|
||||
|
||||
if (options.workspace_sharing !== 'none') {
|
||||
updateFields.workspaceSharingEnabledBy = userId;
|
||||
updateFields.workspaceSharingEnabledAt = new Date().toISOString();
|
||||
} else {
|
||||
updateFields.workspaceSharingEnabledBy = null;
|
||||
updateFields.workspaceSharingEnabledAt = null;
|
||||
}
|
||||
}
|
||||
|
||||
return await updateChat(chatId, updateFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chat by ID (simple version for sharing handlers)
|
||||
*/
|
||||
export async function getChatById(chatId: string): Promise<Chat | null> {
|
||||
const [chat] = await db
|
||||
.select()
|
||||
.from(chats)
|
||||
.where(and(eq(chats.id, chatId), isNull(chats.deletedAt)))
|
||||
.limit(1);
|
||||
|
||||
return chat || null;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ export {
|
|||
updateChat,
|
||||
getChatWithDetails,
|
||||
createMessage,
|
||||
updateChatSharing,
|
||||
getChatById,
|
||||
CreateChatInputSchema,
|
||||
GetChatInputSchema,
|
||||
CreateMessageInputSchema,
|
||||
|
|
|
@ -16,6 +16,8 @@ export {
|
|||
type GetDashboardByIdInput,
|
||||
} from './get-dashboard-by-id';
|
||||
|
||||
export { updateDashboard } from './update-dashboard';
|
||||
|
||||
export {
|
||||
getCollectionsAssociatedWithDashboard,
|
||||
type AssociatedCollection,
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import { and, eq, isNull } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { db } from '../../connection';
|
||||
import { dashboardFiles } from '../../schema';
|
||||
import { WorkspaceSharingSchema } from '../../schema-types';
|
||||
|
||||
// Type for updating dashboardFiles - excludes read-only fields
|
||||
type UpdateDashboardData = Partial<
|
||||
Omit<typeof dashboardFiles.$inferInsert, 'id' | 'createdBy' | 'createdAt' | 'deletedAt'>
|
||||
>;
|
||||
|
||||
// Input validation schema for updating a dashboard
|
||||
const UpdateDashboardInputSchema = z.object({
|
||||
dashboardId: z.string().uuid('Dashboard ID must be a valid UUID'),
|
||||
userId: z.string().uuid('User ID must be a valid UUID'),
|
||||
name: z.string().optional(),
|
||||
publicly_accessible: z.boolean().optional(),
|
||||
public_expiry_date: z.string().nullable().optional(),
|
||||
public_password: z.string().nullable().optional(),
|
||||
workspace_sharing: WorkspaceSharingSchema.optional(),
|
||||
});
|
||||
|
||||
type UpdateDashboardInput = z.infer<typeof UpdateDashboardInputSchema>;
|
||||
|
||||
/**
|
||||
* Updates a dashboard with the provided fields
|
||||
* Only updates fields that are provided in the input
|
||||
* Always updates the updatedAt timestamp
|
||||
*/
|
||||
export const updateDashboard = async (params: UpdateDashboardInput): Promise<void> => {
|
||||
// Validate and destructure input
|
||||
const {
|
||||
dashboardId,
|
||||
userId,
|
||||
name,
|
||||
publicly_accessible,
|
||||
public_expiry_date,
|
||||
public_password,
|
||||
workspace_sharing,
|
||||
} = UpdateDashboardInputSchema.parse(params);
|
||||
|
||||
try {
|
||||
// Build update data - only include fields that are provided
|
||||
const updateData: UpdateDashboardData = {
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Only add fields that are provided
|
||||
if (name !== undefined) {
|
||||
updateData.name = name;
|
||||
}
|
||||
|
||||
if (publicly_accessible !== undefined) {
|
||||
updateData.publiclyAccessible = publicly_accessible;
|
||||
// Set publiclyEnabledBy to userId when enabling, null when disabling
|
||||
updateData.publiclyEnabledBy = publicly_accessible ? userId : null;
|
||||
}
|
||||
|
||||
if (public_expiry_date !== undefined) {
|
||||
updateData.publicExpiryDate = public_expiry_date;
|
||||
}
|
||||
|
||||
if (public_password !== undefined) {
|
||||
updateData.publicPassword = public_password;
|
||||
}
|
||||
|
||||
if (workspace_sharing !== undefined) {
|
||||
updateData.workspaceSharing = workspace_sharing;
|
||||
|
||||
if (workspace_sharing !== 'none') {
|
||||
updateData.workspaceSharingEnabledBy = userId;
|
||||
updateData.workspaceSharingEnabledAt = new Date().toISOString();
|
||||
} else {
|
||||
updateData.workspaceSharingEnabledBy = null;
|
||||
updateData.workspaceSharingEnabledAt = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the dashboard
|
||||
await db
|
||||
.update(dashboardFiles)
|
||||
.set(updateData)
|
||||
.where(and(eq(dashboardFiles.id, dashboardId), isNull(dashboardFiles.deletedAt)));
|
||||
|
||||
console.info(`Successfully updated dashboard ${dashboardId}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to update dashboard ${dashboardId}:`, error);
|
||||
throw new Error(
|
||||
`Failed to update dashboard: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
|
@ -32,6 +32,8 @@ export {
|
|||
type MetricFile,
|
||||
} from './get-metric-by-id';
|
||||
|
||||
export { updateMetric } from './update-metric';
|
||||
|
||||
export {
|
||||
getDashboardsAssociatedWithMetric,
|
||||
getCollectionsAssociatedWithMetric,
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import { and, eq, isNull } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { db } from '../../connection';
|
||||
import { metricFiles } from '../../schema';
|
||||
import { WorkspaceSharingSchema } from '../../schema-types';
|
||||
|
||||
// Type for updating metricFiles - excludes read-only fields
|
||||
type UpdateMetricData = Partial<
|
||||
Omit<typeof metricFiles.$inferInsert, 'id' | 'createdBy' | 'createdAt' | 'deletedAt'>
|
||||
>;
|
||||
|
||||
// Input validation schema for updating a metric
|
||||
const UpdateMetricInputSchema = z.object({
|
||||
metricId: z.string().uuid('Metric ID must be a valid UUID'),
|
||||
userId: z.string().uuid('User ID must be a valid UUID'),
|
||||
name: z.string().optional(),
|
||||
publicly_accessible: z.boolean().optional(),
|
||||
public_expiry_date: z.string().nullable().optional(),
|
||||
public_password: z.string().nullable().optional(),
|
||||
workspace_sharing: WorkspaceSharingSchema.optional(),
|
||||
});
|
||||
|
||||
type UpdateMetricInput = z.infer<typeof UpdateMetricInputSchema>;
|
||||
|
||||
/**
|
||||
* Updates a metric with the provided fields
|
||||
* Only updates fields that are provided in the input
|
||||
* Always updates the updatedAt timestamp
|
||||
*/
|
||||
export const updateMetric = async (params: UpdateMetricInput): Promise<void> => {
|
||||
// Validate and destructure input
|
||||
const {
|
||||
metricId,
|
||||
userId,
|
||||
name,
|
||||
publicly_accessible,
|
||||
public_expiry_date,
|
||||
public_password,
|
||||
workspace_sharing,
|
||||
} = UpdateMetricInputSchema.parse(params);
|
||||
|
||||
try {
|
||||
// Build update data - only include fields that are provided
|
||||
const updateData: UpdateMetricData = {
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Only add fields that are provided
|
||||
if (name !== undefined) {
|
||||
updateData.name = name;
|
||||
}
|
||||
|
||||
if (publicly_accessible !== undefined) {
|
||||
updateData.publiclyAccessible = publicly_accessible;
|
||||
// Set publiclyEnabledBy to userId when enabling, null when disabling
|
||||
updateData.publiclyEnabledBy = publicly_accessible ? userId : null;
|
||||
}
|
||||
|
||||
if (public_expiry_date !== undefined) {
|
||||
updateData.publicExpiryDate = public_expiry_date;
|
||||
}
|
||||
|
||||
if (public_password !== undefined) {
|
||||
updateData.publicPassword = public_password;
|
||||
}
|
||||
|
||||
if (workspace_sharing !== undefined) {
|
||||
updateData.workspaceSharing = workspace_sharing;
|
||||
|
||||
if (workspace_sharing !== 'none') {
|
||||
updateData.workspaceSharingEnabledBy = userId;
|
||||
updateData.workspaceSharingEnabledAt = new Date().toISOString();
|
||||
} else {
|
||||
updateData.workspaceSharingEnabledBy = null;
|
||||
updateData.workspaceSharingEnabledAt = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the metric
|
||||
await db
|
||||
.update(metricFiles)
|
||||
.set(updateData)
|
||||
.where(and(eq(metricFiles.id, metricId), isNull(metricFiles.deletedAt)));
|
||||
|
||||
console.info(`Successfully updated metric ${metricId}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to update metric ${metricId}:`, error);
|
||||
throw new Error(
|
||||
`Failed to update metric: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
};
|
|
@ -9,8 +9,7 @@ export const UpdateMetricResponseSchema = MetricSchema;
|
|||
export const DuplicateMetricResponseSchema = MetricSchema;
|
||||
export const DeleteMetricResponseSchema = z.array(z.string());
|
||||
export const ShareMetricResponseSchema = MetricSchema;
|
||||
export const ShareDeleteResponseSchema = MetricSchema;
|
||||
export const ShareUpdateResponseSchema = MetricSchema;
|
||||
export const ShareMetricUpdateResponseSchema = MetricSchema;
|
||||
|
||||
export const BulkUpdateMetricVerificationStatusResponseSchema = z.object({
|
||||
failed_updates: z.array(MetricSchema),
|
||||
|
@ -36,8 +35,7 @@ export type BulkUpdateMetricVerificationStatusResponse = z.infer<
|
|||
typeof BulkUpdateMetricVerificationStatusResponseSchema
|
||||
>;
|
||||
export type ShareMetricResponse = z.infer<typeof ShareMetricResponseSchema>;
|
||||
export type ShareDeleteResponse = z.infer<typeof ShareDeleteResponseSchema>;
|
||||
export type ShareUpdateResponse = z.infer<typeof ShareUpdateResponseSchema>;
|
||||
export type ShareMetricUpdateResponse = z.infer<typeof ShareMetricUpdateResponseSchema>;
|
||||
export type MetricDataResponse = z.infer<typeof MetricDataResponseSchema>;
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,19 +6,6 @@ export const GetReportsListResponseSchema = PaginatedResponseSchema(ReportListIt
|
|||
export const UpdateReportResponseSchema = ReportResponseSchema;
|
||||
export const ShareUpdateResponseSchema = ReportResponseSchema;
|
||||
|
||||
// Sharing operation response schemas
|
||||
export const SharePostResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
shared: z.array(z.string()),
|
||||
notFound: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const ShareDeleteResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
removed: z.array(z.string()),
|
||||
notFound: z.array(z.string()),
|
||||
});
|
||||
|
||||
// For GET sharing endpoint - matches AssetPermissionWithUser from database
|
||||
export const ShareGetResponseSchema = z.object({
|
||||
permissions: z.array(
|
||||
|
@ -51,6 +38,4 @@ export type GetReportsListResponse = z.infer<typeof GetReportsListResponseSchema
|
|||
export type UpdateReportResponse = z.infer<typeof UpdateReportResponseSchema>;
|
||||
export type GetReportResponse = z.infer<typeof ReportResponseSchema>;
|
||||
export type ShareUpdateResponse = z.infer<typeof ShareUpdateResponseSchema>;
|
||||
export type SharePostResponse = z.infer<typeof SharePostResponseSchema>;
|
||||
export type ShareDeleteResponse = z.infer<typeof ShareDeleteResponseSchema>;
|
||||
export type ShareGetResponse = z.infer<typeof ShareGetResponseSchema>;
|
||||
|
|
|
@ -3,3 +3,4 @@ export * from './verification.types';
|
|||
export * from './requests';
|
||||
export * from './individual-permissions';
|
||||
export * from './assets';
|
||||
export * from './responses';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import z from 'zod';
|
||||
|
||||
// Sharing operation response schemas
|
||||
export const SharePostResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
shared: z.array(z.string()),
|
||||
notFound: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const ShareDeleteResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
removed: z.array(z.string()),
|
||||
notFound: z.array(z.string()),
|
||||
});
|
||||
|
||||
export type SharePostResponse = z.infer<typeof SharePostResponseSchema>;
|
||||
export type ShareDeleteResponse = z.infer<typeof ShareDeleteResponseSchema>;
|
Loading…
Reference in New Issue