mirror of https://github.com/buster-so/buster.git
shortcuts final touches
This commit is contained in:
parent
7d4dc4db63
commit
2b46a49841
|
@ -20,7 +20,7 @@ export async function createShortcutHandler(
|
|||
const { organizationId } = userOrg;
|
||||
|
||||
// Check if user has permission to create workspace shortcuts
|
||||
if (data.sharedWithWorkspace) {
|
||||
if (data.shareWithWorkspace) {
|
||||
// Only workspace_admin or data_admin can create workspace shortcuts
|
||||
if (userOrg.role !== 'workspace_admin' && userOrg.role !== 'data_admin') {
|
||||
throw new HTTPException(403, {
|
||||
|
@ -34,11 +34,11 @@ export async function createShortcutHandler(
|
|||
name: data.name,
|
||||
userId: user.id,
|
||||
organizationId,
|
||||
isWorkspace: data.sharedWithWorkspace,
|
||||
isWorkspace: data.shareWithWorkspace,
|
||||
});
|
||||
|
||||
if (isDuplicate) {
|
||||
const scope = data.sharedWithWorkspace ? 'workspace' : 'your personal shortcuts';
|
||||
const scope = data.shareWithWorkspace ? 'workspace' : 'your personal shortcuts';
|
||||
throw new HTTPException(409, {
|
||||
message: `A shortcut named '${data.name}' already exists in ${scope}`,
|
||||
});
|
||||
|
@ -50,7 +50,7 @@ export async function createShortcutHandler(
|
|||
instructions: data.instructions,
|
||||
createdBy: user.id,
|
||||
organizationId,
|
||||
sharedWithWorkspace: data.sharedWithWorkspace,
|
||||
shareWithWorkspace: data.shareWithWorkspace,
|
||||
});
|
||||
|
||||
if (!shortcut) {
|
||||
|
|
|
@ -36,13 +36,13 @@ export async function deleteShortcutHandler(
|
|||
|
||||
// For personal shortcuts, only creator can delete
|
||||
// For workspace shortcuts, check admin permission (TODO)
|
||||
if (!existingShortcut.sharedWithWorkspace && existingShortcut.createdBy !== user.id) {
|
||||
if (!existingShortcut.shareWithWorkspace && existingShortcut.createdBy !== user.id) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You can only delete your own shortcuts',
|
||||
});
|
||||
}
|
||||
|
||||
if (existingShortcut.sharedWithWorkspace) {
|
||||
if (existingShortcut.shareWithWorkspace) {
|
||||
// TODO: Check if user is admin/has permission to delete workspace shortcuts
|
||||
// For now, we'll allow the creator to delete their workspace shortcuts
|
||||
if (existingShortcut.createdBy !== user.id) {
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function getShortcutHandler(user: User, shortcutId: string): Promis
|
|||
// Check permissions: user must be creator or shortcut must be workspace-shared
|
||||
if (
|
||||
shortcut.organizationId !== organizationId ||
|
||||
(!shortcut.sharedWithWorkspace && shortcut.createdBy !== user.id)
|
||||
(!shortcut.shareWithWorkspace && shortcut.createdBy !== user.id)
|
||||
) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You do not have permission to view this shortcut',
|
||||
|
|
|
@ -42,14 +42,14 @@ export async function updateShortcutHandler(
|
|||
}
|
||||
|
||||
// For personal shortcuts, only creator can update
|
||||
if (!existingShortcut.sharedWithWorkspace && existingShortcut.createdBy !== user.id) {
|
||||
if (!existingShortcut.shareWithWorkspace && existingShortcut.createdBy !== user.id) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'You can only update your own shortcuts',
|
||||
});
|
||||
}
|
||||
|
||||
// For workspace shortcuts, check admin permission
|
||||
if (existingShortcut.sharedWithWorkspace) {
|
||||
if (existingShortcut.shareWithWorkspace) {
|
||||
// Only workspace_admin, data_admin, or the creator can update workspace shortcuts
|
||||
const isAdmin = userOrg.role === 'workspace_admin' || userOrg.role === 'data_admin';
|
||||
const isCreator = existingShortcut.createdBy === user.id;
|
||||
|
@ -62,18 +62,36 @@ export async function updateShortcutHandler(
|
|||
}
|
||||
}
|
||||
|
||||
// Check permission to change sharing status
|
||||
if (data.shareWithWorkspace !== undefined && data.shareWithWorkspace !== existingShortcut.shareWithWorkspace) {
|
||||
// Only admins can change sharing status
|
||||
const isAdmin = userOrg.role === 'workspace_admin' || userOrg.role === 'data_admin';
|
||||
|
||||
if (!isAdmin) {
|
||||
throw new HTTPException(403, {
|
||||
message: 'Only workspace admins and data admins can change shortcut sharing settings',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the scope for duplicate checking
|
||||
const willBeWorkspaceShortcut =
|
||||
data.shareWithWorkspace !== undefined
|
||||
? data.shareWithWorkspace
|
||||
: existingShortcut.shareWithWorkspace;
|
||||
|
||||
// If name is being changed, check for duplicates
|
||||
if (data.name && data.name !== existingShortcut.name) {
|
||||
const isDuplicate = await checkDuplicateName({
|
||||
name: data.name,
|
||||
userId: user.id,
|
||||
organizationId,
|
||||
isWorkspace: existingShortcut.sharedWithWorkspace,
|
||||
isWorkspace: willBeWorkspaceShortcut,
|
||||
excludeId: shortcutId,
|
||||
});
|
||||
|
||||
if (isDuplicate) {
|
||||
const scope = existingShortcut.sharedWithWorkspace
|
||||
const scope = willBeWorkspaceShortcut
|
||||
? 'workspace'
|
||||
: 'your personal shortcuts';
|
||||
throw new HTTPException(409, {
|
||||
|
@ -87,6 +105,7 @@ export async function updateShortcutHandler(
|
|||
id: shortcutId,
|
||||
name: data.name,
|
||||
instructions: data.instructions,
|
||||
shareWithWorkspace: data.shareWithWorkspace,
|
||||
updatedBy: user.id,
|
||||
});
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@ CREATE TABLE "shortcuts" (
|
|||
"created_by" uuid NOT NULL,
|
||||
"updated_by" uuid,
|
||||
"organization_id" uuid NOT NULL,
|
||||
"shared_with_workspace" boolean DEFAULT false NOT NULL,
|
||||
"share_with_workspace" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"deleted_at" timestamp with time zone,
|
||||
CONSTRAINT "shortcuts_personal_unique" UNIQUE("name","organization_id","created_by")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "suggested_prompts" SET DEFAULT '{"suggestedPrompts":{"report":["provide a trend analysis of quarterly profits","evaluate product performance across regions"],"dashboard":["create a sales performance dashboard","design a revenue forecast dashboard"],"visualization":["create a metric for monthly sales","show top vendors by purchase volume"],"help":["what types of analyses can you perform?","what questions can I as buster?","what data models are available for queries?","can you explain your forecasting capabilities?"]},"updatedAt":"2025-09-12T16:57:43.191Z"}'::jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "users" ALTER COLUMN "suggested_prompts" SET DEFAULT '{"suggestedPrompts":{"report":["provide a trend analysis of quarterly profits","evaluate product performance across regions"],"dashboard":["create a sales performance dashboard","design a revenue forecast dashboard"],"visualization":["create a metric for monthly sales","show top vendors by purchase volume"],"help":["what types of analyses can you perform?","what questions can I as buster?","what data models are available for queries?","can you explain your forecasting capabilities?"]},"updatedAt":"2025-09-12T17:22:58.487Z"}'::jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "messages" ADD COLUMN "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "users" ADD COLUMN "last_used_shortcuts" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "shortcuts" ADD CONSTRAINT "shortcuts_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade;--> statement-breakpoint
|
||||
|
@ -20,4 +20,4 @@ ALTER TABLE "shortcuts" ADD CONSTRAINT "shortcuts_updated_by_fkey" FOREIGN KEY (
|
|||
ALTER TABLE "shortcuts" ADD CONSTRAINT "shortcuts_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "shortcuts_org_user_idx" ON "shortcuts" USING btree ("organization_id" uuid_ops,"created_by" uuid_ops);--> statement-breakpoint
|
||||
CREATE INDEX "shortcuts_name_idx" ON "shortcuts" USING btree ("name");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "shortcuts_workspace_unique" ON "shortcuts" USING btree ("name","organization_id") WHERE "shortcuts"."shared_with_workspace" = true;
|
||||
CREATE UNIQUE INDEX "shortcuts_workspace_unique" ON "shortcuts" USING btree ("name","organization_id") WHERE "shortcuts"."share_with_workspace" = true;
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"id": "0613296e-fdd2-4a35-9d79-2275d0c7a78e",
|
||||
"id": "155b0fb6-d5e9-46bc-aadd-f71a6f9e48f7",
|
||||
"prevId": "f71f7e1c-314d-413d-8da9-a525bd4a3519",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
|
@ -5043,8 +5043,8 @@
|
|||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"shared_with_workspace": {
|
||||
"name": "shared_with_workspace",
|
||||
"share_with_workspace": {
|
||||
"name": "share_with_workspace",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
|
@ -5127,7 +5127,7 @@
|
|||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"shortcuts\".\"shared_with_workspace\" = true",
|
||||
"where": "\"shortcuts\".\"share_with_workspace\" = true",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
|
@ -6914,7 +6914,7 @@
|
|||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{\"suggestedPrompts\":{\"report\":[\"provide a trend analysis of quarterly profits\",\"evaluate product performance across regions\"],\"dashboard\":[\"create a sales performance dashboard\",\"design a revenue forecast dashboard\"],\"visualization\":[\"create a metric for monthly sales\",\"show top vendors by purchase volume\"],\"help\":[\"what types of analyses can you perform?\",\"what questions can I as buster?\",\"what data models are available for queries?\",\"can you explain your forecasting capabilities?\"]},\"updatedAt\":\"2025-09-12T16:57:43.191Z\"}'::jsonb"
|
||||
"default": "'{\"suggestedPrompts\":{\"report\":[\"provide a trend analysis of quarterly profits\",\"evaluate product performance across regions\"],\"dashboard\":[\"create a sales performance dashboard\",\"design a revenue forecast dashboard\"],\"visualization\":[\"create a metric for monthly sales\",\"show top vendors by purchase volume\"],\"help\":[\"what types of analyses can you perform?\",\"what questions can I as buster?\",\"what data models are available for queries?\",\"can you explain your forecasting capabilities?\"]},\"updatedAt\":\"2025-09-12T17:22:58.487Z\"}'::jsonb"
|
||||
},
|
||||
"personalization_enabled": {
|
||||
"name": "personalization_enabled",
|
||||
|
|
|
@ -677,8 +677,8 @@
|
|||
{
|
||||
"idx": 96,
|
||||
"version": "7",
|
||||
"when": 1757696263222,
|
||||
"tag": "0096_outgoing_shotgun",
|
||||
"when": 1757697778519,
|
||||
"tag": "0096_chubby_agent_zero",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
@ -29,7 +29,7 @@ export async function checkDuplicateName(input: CheckDuplicateNameInput): Promis
|
|||
|
||||
if (validated.isWorkspace) {
|
||||
// Check for existing workspace shortcut
|
||||
conditions.push(eq(shortcuts.sharedWithWorkspace, true));
|
||||
conditions.push(eq(shortcuts.shareWithWorkspace, true));
|
||||
} else {
|
||||
// Check for existing personal shortcut
|
||||
conditions.push(eq(shortcuts.createdBy, validated.userId));
|
||||
|
|
|
@ -8,7 +8,7 @@ export const CreateShortcutInputSchema = z.object({
|
|||
instructions: z.string().min(1),
|
||||
createdBy: z.string().uuid(),
|
||||
organizationId: z.string().uuid(),
|
||||
sharedWithWorkspace: z.boolean(),
|
||||
shareWithWorkspace: z.boolean(),
|
||||
});
|
||||
|
||||
export type CreateShortcutInput = z.infer<typeof CreateShortcutInputSchema>;
|
||||
|
@ -24,8 +24,8 @@ export async function createShortcut(input: CreateShortcutInput) {
|
|||
and(
|
||||
eq(shortcuts.name, validated.name),
|
||||
eq(shortcuts.organizationId, validated.organizationId),
|
||||
validated.sharedWithWorkspace
|
||||
? eq(shortcuts.sharedWithWorkspace, true)
|
||||
validated.shareWithWorkspace
|
||||
? eq(shortcuts.shareWithWorkspace, true)
|
||||
: eq(shortcuts.createdBy, validated.createdBy),
|
||||
isNotNull(shortcuts.deletedAt)
|
||||
)
|
||||
|
@ -38,7 +38,7 @@ export async function createShortcut(input: CreateShortcutInput) {
|
|||
.update(shortcuts)
|
||||
.set({
|
||||
instructions: validated.instructions,
|
||||
sharedWithWorkspace: validated.sharedWithWorkspace,
|
||||
shareWithWorkspace: validated.shareWithWorkspace,
|
||||
updatedBy: validated.createdBy,
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
|
@ -58,7 +58,7 @@ export async function createShortcut(input: CreateShortcutInput) {
|
|||
createdBy: validated.createdBy,
|
||||
updatedBy: validated.createdBy,
|
||||
organizationId: validated.organizationId,
|
||||
sharedWithWorkspace: validated.sharedWithWorkspace,
|
||||
shareWithWorkspace: validated.shareWithWorkspace,
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ export async function findShortcutByName(input: FindShortcutByNameInput) {
|
|||
and(
|
||||
eq(shortcuts.name, validated.name),
|
||||
eq(shortcuts.organizationId, validated.organizationId),
|
||||
eq(shortcuts.sharedWithWorkspace, true),
|
||||
eq(shortcuts.shareWithWorkspace, true),
|
||||
isNull(shortcuts.deletedAt)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -19,7 +19,7 @@ export async function getUserShortcuts(input: GetUserShortcutsInput) {
|
|||
.where(
|
||||
and(
|
||||
eq(shortcuts.organizationId, validated.organizationId),
|
||||
or(eq(shortcuts.createdBy, validated.userId), eq(shortcuts.sharedWithWorkspace, true)),
|
||||
or(eq(shortcuts.createdBy, validated.userId), eq(shortcuts.shareWithWorkspace, true)),
|
||||
isNull(shortcuts.deletedAt)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ export const UpdateShortcutInputSchema = z.object({
|
|||
id: z.string().uuid(),
|
||||
name: z.string().min(1).max(255).optional(),
|
||||
instructions: z.string().min(1).optional(),
|
||||
shareWithWorkspace: z.boolean().optional(),
|
||||
updatedBy: z.string().uuid(),
|
||||
});
|
||||
|
||||
|
@ -28,6 +29,10 @@ export async function updateShortcut(input: UpdateShortcutInput) {
|
|||
updateData.instructions = validated.instructions;
|
||||
}
|
||||
|
||||
if (validated.shareWithWorkspace !== undefined) {
|
||||
updateData.shareWithWorkspace = validated.shareWithWorkspace;
|
||||
}
|
||||
|
||||
const [updated] = await db
|
||||
.update(shortcuts)
|
||||
.set(updateData)
|
||||
|
|
|
@ -2327,7 +2327,7 @@ export const shortcuts = pgTable(
|
|||
createdBy: uuid('created_by').notNull(),
|
||||
updatedBy: uuid('updated_by'),
|
||||
organizationId: uuid('organization_id').notNull(),
|
||||
sharedWithWorkspace: boolean('shared_with_workspace').default(false).notNull(),
|
||||
shareWithWorkspace: boolean('share_with_workspace').default(false).notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' })
|
||||
.defaultNow()
|
||||
.notNull(),
|
||||
|
@ -2365,7 +2365,7 @@ export const shortcuts = pgTable(
|
|||
// Conditional unique constraint for workspace shortcuts
|
||||
uniqueIndex('shortcuts_workspace_unique')
|
||||
.on(table.name, table.organizationId)
|
||||
.where(sql`${table.sharedWithWorkspace} = true`),
|
||||
.where(sql`${table.shareWithWorkspace} = true`),
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export const createShortcutRequestSchema = z.object({
|
|||
.string()
|
||||
.min(1, 'Instructions are required')
|
||||
.max(10000, 'Instructions must be 10,000 characters or less'),
|
||||
sharedWithWorkspace: z.boolean(),
|
||||
shareWithWorkspace: z.boolean(),
|
||||
});
|
||||
|
||||
export const updateShortcutRequestSchema = z.object({
|
||||
|
@ -29,6 +29,7 @@ export const updateShortcutRequestSchema = z.object({
|
|||
.min(1, 'Instructions are required')
|
||||
.max(10000, 'Instructions must be 10,000 characters or less')
|
||||
.optional(),
|
||||
shareWithWorkspace: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// Export types inferred from schemas
|
||||
|
|
|
@ -7,7 +7,7 @@ export const shortcutSchema = z.object({
|
|||
createdBy: z.string().uuid(),
|
||||
updatedBy: z.string().uuid().nullable(),
|
||||
organizationId: z.string().uuid(),
|
||||
sharedWithWorkspace: z.boolean(),
|
||||
shareWithWorkspace: z.boolean(),
|
||||
createdAt: z.string(), // ISO string
|
||||
updatedAt: z.string(), // ISO string
|
||||
deletedAt: z.string().nullable(), // ISO string or null
|
||||
|
|
Loading…
Reference in New Issue