adding the public_password field for chats table

This commit is contained in:
Wells Bunker 2025-09-29 11:48:22 -06:00
parent 1f4276e6ee
commit 16892fe64e
No known key found for this signature in database
GPG Key ID: DB16D6F2679B78FC
10 changed files with 5859 additions and 30 deletions

View File

@ -1,12 +1,10 @@
import { checkPermission } from '@buster/access-controls';
import type { User } from '@buster/database/queries';
import { getChatWithDetails, getMessagesForChatWithUserDetails } from '@buster/database/queries';
import {
getChatWithDetails,
getMessagesForChatWithUserDetails,
} from '@buster/database/queries';
import {
GetChatRequestParamsSchema,
GetChatRequestQuerySchema,
type GetChatResponse,
GetChatRequestSchema,
} from '@buster/server-shared/chats';
import { zValidator } from '@hono/zod-validator';
import { Hono } from 'hono';
@ -17,18 +15,34 @@ import { buildChatWithMessages } from '../services/chat-helpers';
interface GetChatHandlerParams {
chatId: string;
user: User;
userSuppliedPassword?: string;
}
const app = new Hono().get('/', zValidator('param', GetChatRequestSchema), async (c) => {
const app = new Hono().get(
'/',
zValidator('param', GetChatRequestParamsSchema),
zValidator('query', GetChatRequestQuerySchema),
async (c) => {
const { id } = c.req.valid('param');
const { password } = c.req.valid('query');
const user = c.get('busterUser');
console.info(`Processing GET request for chat with ID: ${id}, user_id: ${user.id}`);
const response: GetChatResponse = await getChatHandler({ chatId: id, user });
const getChatHandlerParams: GetChatHandlerParams = {
chatId: id,
user,
};
if (password) {
getChatHandlerParams.userSuppliedPassword = password;
}
const response: GetChatResponse = await getChatHandler(getChatHandlerParams);
return c.json(response);
});
}
);
export default app;
@ -37,7 +51,7 @@ export default app;
* This is the TypeScript equivalent of the Rust get_chat_handler
*/
export async function getChatHandler(params: GetChatHandlerParams): Promise<GetChatResponse> {
const { chatId, user } = params;
const { chatId, user, userSuppliedPassword } = params;
// Fetch chat with messages and related data
const chatData = await getChatWithDetails({
@ -64,6 +78,8 @@ export async function getChatHandler(params: GetChatHandlerParams): Promise<GetC
workspaceSharing: chat.workspaceSharing || 'none',
publiclyAccessible: chat.publiclyAccessible || false,
publicExpiryDate: chat.publicExpiryDate ?? undefined,
publicPassword: chat.publicPassword ?? undefined,
userSuppliedPassword,
});
if (!hasAccess || !effectiveRole) {

View File

@ -203,7 +203,7 @@ export async function buildChatWithMessages(
publicly_accessible: chat.publiclyAccessible || false,
public_expiry_date: chat.publicExpiryDate || null,
public_enabled_by: publiclyEnabledBy,
public_password: null, // password not implemented yet
public_password: chat.publicPassword || null,
permission,
workspace_sharing: chat.workspaceSharing || 'none',
workspace_member_count: workspaceMemberCount,

View File

@ -2,7 +2,7 @@ import type {
DeleteChatsRequest,
DuplicateChatRequest,
DuplicateChatResponse,
GetChatRequest,
GetChatRequestParams,
GetChatResponse,
GetLogsListRequest,
GetLogsListResponse,
@ -30,7 +30,7 @@ export const getListLogs = async (params?: GetLogsListRequest): Promise<GetLogsL
.then((res) => res.data);
};
export const getChat = async ({ id }: GetChatRequest): Promise<GetChatResponse> => {
export const getChat = async ({ id }: GetChatRequestParams): Promise<GetChatResponse> => {
return mainApiV2.get<GetChatResponse>(`${CHATS_BASE}/${id}`).then((res) => res.data);
};

View File

@ -62,7 +62,11 @@ export async function checkMetricDashboardAccess(
* Check if a user has access to a metric through any chat that contains it.
* If a user has access to a chat (direct, public, or workspace), they can view the metrics in it.
*/
export async function checkMetricChatAccess(metricId: string, user: User): Promise<boolean> {
export async function checkMetricChatAccess(
metricId: string,
user: User,
userSuppliedPassword?: string
): Promise<boolean> {
try {
// Get all chats containing this metric with their workspace sharing info
const chats = await checkChatsContainingAsset(metricId, 'metric_file');
@ -82,8 +86,8 @@ export async function checkMetricChatAccess(metricId: string, user: User): Promi
workspaceSharing: (chat.workspaceSharing as WorkspaceSharing) ?? 'none',
publiclyAccessible: chat.publiclyAccessible,
publicExpiryDate: chat.publicExpiryDate ?? undefined,
publicPassword: undefined, // We don't support passwords on the chats table
userSuppliedPassword: undefined, // We don't support passwords on the chats table
publicPassword: chat.publicPassword ?? undefined,
userSuppliedPassword: userSuppliedPassword,
});
if (hasAccess) {
@ -152,7 +156,11 @@ export async function checkMetricReportAccess(
* Check if a user has access to a dashboard through any chat that contains it.
* If a user has access to a chat (direct, public, or workspace), they can view the dashboards in it.
*/
export async function checkDashboardChatAccess(dashboardId: string, user: User): Promise<boolean> {
export async function checkDashboardChatAccess(
dashboardId: string,
user: User,
userSuppliedPassword?: string
): Promise<boolean> {
try {
// Get all chats containing this dashboard with their workspace sharing info
const chats = await checkChatsContainingAsset(dashboardId, 'dashboard_file');
@ -172,8 +180,8 @@ export async function checkDashboardChatAccess(dashboardId: string, user: User):
workspaceSharing: (chat.workspaceSharing as WorkspaceSharing) ?? 'none',
publiclyAccessible: chat.publiclyAccessible,
publicExpiryDate: chat.publicExpiryDate ?? undefined,
publicPassword: undefined, // We don't support passwords on the chats table
userSuppliedPassword: undefined, // We don't support passwords on the chats table
publicPassword: chat.publicPassword ?? undefined,
userSuppliedPassword: userSuppliedPassword,
});
if (hasAccess) {
@ -315,7 +323,11 @@ export async function checkChatCollectionAccess(chatId: string, user: User): Pro
* Check if a user has access to a report through any chat that contains it.
* If a user has access to a chat (direct, public, or workspace), they can view the reports in it.
*/
export async function checkReportChatAccess(reportId: string, user: User): Promise<boolean> {
export async function checkReportChatAccess(
reportId: string,
user: User,
userSuppliedPassword?: string
): Promise<boolean> {
try {
// Get all chats containing this dashboard with their workspace sharing info
const chats = await checkChatsContainingAsset(reportId, 'report_file');
@ -335,8 +347,8 @@ export async function checkReportChatAccess(reportId: string, user: User): Promi
workspaceSharing: (chat.workspaceSharing as WorkspaceSharing) ?? 'none',
publiclyAccessible: chat.publiclyAccessible,
publicExpiryDate: chat.publicExpiryDate ?? undefined,
publicPassword: undefined, // We don't support passwords on the chats table
userSuppliedPassword: undefined, // We don't support passwords on the chats table
publicPassword: chat.publicPassword ?? undefined,
userSuppliedPassword: userSuppliedPassword,
});
if (hasAccess) {
@ -425,7 +437,7 @@ export async function checkCascadingPermissions(
break;
}
const chatAccess = await checkMetricChatAccess(assetId, user);
const chatAccess = await checkMetricChatAccess(assetId, user, userSuppliedPassword);
if (chatAccess) {
hasAccess = true;
break;
@ -447,7 +459,11 @@ export async function checkCascadingPermissions(
case 'dashboard_file': {
// Check access through chats and collections
const dashboardChatAccess = await checkDashboardChatAccess(assetId, user);
const dashboardChatAccess = await checkDashboardChatAccess(
assetId,
user,
userSuppliedPassword
);
if (dashboardChatAccess) {
hasAccess = true;
break;
@ -473,7 +489,7 @@ export async function checkCascadingPermissions(
case 'report_file': {
// Check access through chats and collections
const reportChatAccess = await checkReportChatAccess(assetId, user);
const reportChatAccess = await checkReportChatAccess(assetId, user, userSuppliedPassword);
if (reportChatAccess) {
hasAccess = true;
break;

View File

@ -0,0 +1 @@
ALTER TABLE "chats" ADD COLUMN "public_password" text;

File diff suppressed because it is too large Load Diff

View File

@ -771,6 +771,13 @@
"when": 1758658933810,
"tag": "0110_third_ozymandias",
"breakpoints": true
},
{
"idx": 111,
"version": "7",
"when": 1759167570252,
"tag": "0111_happy_peter_quill",
"breakpoints": true
}
]
}

View File

@ -9,6 +9,7 @@ export interface ChatWithSharing {
workspaceSharing: WorkspaceSharing | null;
publiclyAccessible: boolean;
publicExpiryDate: string | null;
publicPassword: string | null;
}
export async function checkChatsContainingAsset(
@ -22,6 +23,7 @@ export async function checkChatsContainingAsset(
workspaceSharing: chats.workspaceSharing,
publiclyAccessible: chats.publiclyAccessible,
publicExpiryDate: chats.publicExpiryDate,
publicPassword: chats.publicPassword,
})
.from(messagesToFiles)
.innerJoin(messages, eq(messages.id, messagesToFiles.messageId))

View File

@ -847,6 +847,7 @@ export const chats = pgTable(
withTimezone: true,
mode: 'string',
}),
publicPassword: text('public_password'),
mostRecentFileId: uuid('most_recent_file_id'),
mostRecentFileType: assetTypeEnum('most_recent_file_type'),
mostRecentVersionNumber: integer('most_recent_version_number'),

View File

@ -13,11 +13,16 @@ export const GetChatsRequestSchemaV2 = PaginatedRequestSchema;
export type GetChatsRequestV2 = z.infer<typeof GetChatsRequestSchemaV2>;
// Request for getting a single chat
export const GetChatRequestSchema = z.object({
export const GetChatRequestParamsSchema = z.object({
id: z.string(),
});
export type GetChatRequest = z.infer<typeof GetChatRequestSchema>;
export const GetChatRequestQuerySchema = z.object({
password: z.string().optional(),
});
export type GetChatRequestQuery = z.infer<typeof GetChatRequestQuerySchema>;
export type GetChatRequestParams = z.infer<typeof GetChatRequestParamsSchema>;
// Request for deleting multiple chats
export const DeleteChatsRequestSchema = z.array(z.string());