Skip to content

Commit 018402d

Browse files
committed
v2.8.0: Keybindings settings, discard checkout, realtime admin sync
Added: - Keybindings settings page with new keybinding features - Discard checkout on right click menu - Admin setting changes now sync instantly across all connected clients via Supabase Realtime subscriptions Changed: - File Operation Icons Overhaul: Redesigned file status and action icons for improved clarity - SolidWorks Integration Improvements: Enhanced stability and performance - Odoo Configuration Improvements: Streamlined setup with better validation Fixed: - Organization settings not saving: Added missing RLS UPDATE policy for organizations table
1 parent f41a447 commit 018402d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+8576
-1906
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
All notable changes to BluePLM will be documented in this file.
44

5+
## [2.8.0] - 2025-12-16
6+
7+
### Added
8+
- Keybindings settings page with new keybinding features
9+
- Discard checkout on right click menu
10+
- Admin setting changes now sync instantly across all connected clients via Supabase Realtime subscriptions
11+
12+
### Changed
13+
- **File Operation Icons Overhaul**: Redesigned file status and action icons for improved clarity and visual consistency across the file browser
14+
- **SolidWorks Integration Improvements**: Enhanced stability and performance for SW metadata extraction and file operations
15+
- **Odoo Configuration Improvements**: Streamlined Odoo integration setup with better validation and error handling
16+
17+
### Fixed
18+
- **Organization settings not saving**: Added missing RLS UPDATE policy for organizations table. Admin updates to organization settings (e.g., SolidWorks DM license key) were silently blocked by Row Level Security.
19+
20+
---
21+
522
## [2.7.2] - 2025-12-16
623

724
### Changed

api/server.ts

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ import { createClient, SupabaseClient, User as SupabaseUser } from '@supabase/su
4949
import crypto from 'crypto'
5050
import fs from 'fs'
5151
import path from 'path'
52+
import { fileURLToPath } from 'url'
53+
54+
// ESM-compatible __dirname
55+
const __filename = fileURLToPath(import.meta.url)
56+
const __dirname = path.dirname(__filename)
5257

5358
// Load version from API's own package.json
5459
const packageJsonPath = path.join(__dirname, 'package.json')
@@ -3538,13 +3543,97 @@ export async function buildServer(): Promise<FastifyInstance> {
35383543
}
35393544
}
35403545

3541-
// Always save the settings (even if connection failed)
3546+
// Check if this config already exists in saved configs
3547+
// Compare url, database, username, and api_key to detect if it's a new/different config
3548+
const { data: existingConfigs } = await request.supabase!
3549+
.from('odoo_saved_configs')
3550+
.select('id, url, database, username, api_key_encrypted')
3551+
.eq('org_id', request.user.org_id)
3552+
.eq('is_active', true)
3553+
3554+
// Check if any saved config matches the current settings
3555+
const matchingConfig = existingConfigs?.find(config =>
3556+
config.url === normalizedUrl &&
3557+
config.database === database &&
3558+
config.username === username &&
3559+
config.api_key_encrypted === api_key
3560+
)
3561+
3562+
let newConfigId: string | null = null
3563+
let newConfigName: string | null = null
3564+
3565+
// If no matching config exists, create a new saved config automatically
3566+
if (!matchingConfig) {
3567+
// Generate a unique name based on existing configs
3568+
const existingNames = existingConfigs?.map(c => c.url) || []
3569+
let baseName = `${normalizedUrl.replace(/^https?:\/\//, '').split('/')[0]}`
3570+
let configName = baseName
3571+
let counter = 1
3572+
3573+
// Check against existing saved config names
3574+
const { data: existingNamedConfigs } = await request.supabase!
3575+
.from('odoo_saved_configs')
3576+
.select('name')
3577+
.eq('org_id', request.user.org_id)
3578+
.eq('is_active', true)
3579+
3580+
const usedNames = existingNamedConfigs?.map(c => c.name) || []
3581+
while (usedNames.includes(configName)) {
3582+
counter++
3583+
configName = `${baseName} (${counter})`
3584+
}
3585+
3586+
// Pick a color based on index
3587+
const colors = ['#22c55e', '#3b82f6', '#8b5cf6', '#f97316', '#ec4899', '#06b6d4', '#eab308', '#ef4444']
3588+
const colorIndex = (existingConfigs?.length || 0) % colors.length
3589+
3590+
const { data: newConfig, error: configError } = await request.supabase!
3591+
.from('odoo_saved_configs')
3592+
.insert({
3593+
org_id: request.user.org_id,
3594+
name: configName,
3595+
url: normalizedUrl,
3596+
database,
3597+
username,
3598+
api_key_encrypted: api_key,
3599+
color: colors[colorIndex],
3600+
is_active: true,
3601+
last_tested_at: !skip_test ? new Date().toISOString() : null,
3602+
last_test_success: !skip_test ? isConnected : null,
3603+
last_test_error: !skip_test ? connectionError : null,
3604+
created_by: request.user.id,
3605+
updated_by: request.user.id
3606+
})
3607+
.select('id, name')
3608+
.single()
3609+
3610+
if (!configError && newConfig) {
3611+
newConfigId = newConfig.id
3612+
newConfigName = newConfig.name
3613+
}
3614+
} else {
3615+
// Update the test status on the matching config
3616+
if (!skip_test) {
3617+
await request.supabase!
3618+
.from('odoo_saved_configs')
3619+
.update({
3620+
last_tested_at: new Date().toISOString(),
3621+
last_test_success: isConnected,
3622+
last_test_error: connectionError,
3623+
updated_by: request.user.id
3624+
})
3625+
.eq('id', matchingConfig.id)
3626+
}
3627+
newConfigId = matchingConfig.id
3628+
}
3629+
3630+
// Always save the settings to organization_integrations (even if connection failed)
35423631
const { error } = await request.supabase!
35433632
.from('organization_integrations')
35443633
.upsert({
35453634
org_id: request.user.org_id,
35463635
integration_type: 'odoo',
3547-
settings: { url: normalizedUrl, database, username },
3636+
settings: { url: normalizedUrl, database, username, config_id: newConfigId, config_name: newConfigName || matchingConfig?.url },
35483637
credentials_encrypted: api_key, // In production, encrypt this
35493638
is_active: true,
35503639
is_connected: isConnected,
@@ -3559,11 +3648,17 @@ export async function buildServer(): Promise<FastifyInstance> {
35593648
if (error) throw error
35603649

35613650
if (skip_test) {
3562-
return { success: true, message: 'Odoo credentials saved (connection not tested)' }
3651+
const msg = newConfigName
3652+
? `Odoo credentials saved as "${newConfigName}" (connection not tested)`
3653+
: 'Odoo credentials saved (connection not tested)'
3654+
return { success: true, message: msg, new_config: newConfigName ? { id: newConfigId, name: newConfigName } : undefined }
35633655
} else if (isConnected) {
3564-
return { success: true, message: 'Odoo integration configured and connected!' }
3656+
const msg = newConfigName
3657+
? `Odoo integration connected and saved as "${newConfigName}"!`
3658+
: 'Odoo integration configured and connected!'
3659+
return { success: true, message: msg, new_config: newConfigName ? { id: newConfigId, name: newConfigName } : undefined }
35653660
} else {
3566-
return { success: true, message: `Credentials saved but connection failed: ${connectionError}`, connection_error: connectionError }
3661+
return { success: true, message: `Credentials saved but connection failed: ${connectionError}`, connection_error: connectionError, new_config: newConfigName ? { id: newConfigId, name: newConfigName } : undefined }
35673662
}
35683663
})
35693664

0 commit comments

Comments
 (0)