🔄 Database Auto-Migration:
- Add automatic schema update in user preferences API
- Ensures ocr_settings column exists when first accessed
- Uses ADD COLUMN IF NOT EXISTS for safe migration
- Handles case where column might already exist
- No need for separate migration endpoint
This ensures the settings page will work immediately after deployment
without requiring manual database updates.
219 lines
No EOL
7 KiB
JavaScript
219 lines
No EOL
7 KiB
JavaScript
const { Pool } = require('pg');
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
const pool = new Pool({
|
|
connectionString: process.env.DATABASE_URL,
|
|
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
|
});
|
|
|
|
// Verify JWT token and extract user info
|
|
function verifyAuth(req) {
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
throw new Error('No authorization token provided');
|
|
}
|
|
|
|
const token = authHeader.substring(7);
|
|
const jwtSecret = process.env.JWT_SECRET || 'fallback-secret-change-in-production';
|
|
|
|
try {
|
|
const decoded = jwt.verify(token, jwtSecret);
|
|
return decoded;
|
|
} catch (error) {
|
|
throw new Error('Invalid or expired token');
|
|
}
|
|
}
|
|
|
|
export default async function handler(req, res) {
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
const user = verifyAuth(req);
|
|
|
|
// Ensure ocr_settings column exists (auto-migration)
|
|
try {
|
|
await client.query(`
|
|
ALTER TABLE user_preferences
|
|
ADD COLUMN IF NOT EXISTS ocr_settings JSONB DEFAULT '{
|
|
"preferred_service": "openai",
|
|
"openai_api_key": "",
|
|
"ollama_url": "http://localhost:11434",
|
|
"auto_add_to_collection": false,
|
|
"confidence_threshold": 80
|
|
}'::jsonb;
|
|
`);
|
|
} catch (error) {
|
|
// Column might already exist, ignore error
|
|
console.log('OCR settings column may already exist:', error.message);
|
|
}
|
|
|
|
if (req.method === 'GET') {
|
|
// Get user preferences
|
|
const result = await client.query(`
|
|
SELECT
|
|
default_view,
|
|
items_per_page,
|
|
enable_animations,
|
|
enable_ocr,
|
|
theme,
|
|
privacy_settings,
|
|
ocr_settings,
|
|
created_at,
|
|
updated_at
|
|
FROM user_preferences
|
|
WHERE user_id = $1
|
|
`, [user.userId]);
|
|
|
|
if (result.rows.length === 0) {
|
|
// Create default preferences if none exist
|
|
const defaultPrefs = {
|
|
default_view: 'card',
|
|
items_per_page: 20,
|
|
enable_animations: true,
|
|
enable_ocr: true,
|
|
theme: 'light',
|
|
privacy_settings: { collections_public: false, decks_public: false },
|
|
ocr_settings: {
|
|
preferred_service: 'openai',
|
|
openai_api_key: '',
|
|
ollama_url: 'http://localhost:11434',
|
|
auto_add_to_collection: false,
|
|
confidence_threshold: 80
|
|
}
|
|
};
|
|
|
|
await client.query(`
|
|
INSERT INTO user_preferences (
|
|
user_id, default_view, items_per_page, enable_animations,
|
|
enable_ocr, theme, privacy_settings, ocr_settings
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
`, [
|
|
user.userId,
|
|
defaultPrefs.default_view,
|
|
defaultPrefs.items_per_page,
|
|
defaultPrefs.enable_animations,
|
|
defaultPrefs.enable_ocr,
|
|
defaultPrefs.theme,
|
|
JSON.stringify(defaultPrefs.privacy_settings),
|
|
JSON.stringify(defaultPrefs.ocr_settings)
|
|
]);
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
preferences: defaultPrefs
|
|
});
|
|
}
|
|
|
|
const preferences = result.rows[0];
|
|
res.status(200).json({
|
|
success: true,
|
|
preferences: {
|
|
defaultView: preferences.default_view,
|
|
itemsPerPage: preferences.items_per_page,
|
|
enableAnimations: preferences.enable_animations,
|
|
enableOcr: preferences.enable_ocr,
|
|
theme: preferences.theme,
|
|
privacySettings: preferences.privacy_settings,
|
|
ocrSettings: preferences.ocr_settings || {
|
|
preferred_service: 'openai',
|
|
openai_api_key: '',
|
|
ollama_url: 'http://localhost:11434',
|
|
auto_add_to_collection: false,
|
|
confidence_threshold: 80
|
|
},
|
|
createdAt: preferences.created_at,
|
|
updatedAt: preferences.updated_at
|
|
}
|
|
});
|
|
|
|
} else if (req.method === 'PUT') {
|
|
// Update user preferences
|
|
const {
|
|
defaultView,
|
|
itemsPerPage,
|
|
enableAnimations,
|
|
enableOcr,
|
|
theme,
|
|
privacySettings,
|
|
ocrSettings
|
|
} = req.body;
|
|
|
|
// Validate OCR settings if provided
|
|
if (ocrSettings) {
|
|
const allowedServices = ['openai', 'ollama'];
|
|
if (ocrSettings.preferred_service && !allowedServices.includes(ocrSettings.preferred_service)) {
|
|
return res.status(400).json({
|
|
error: 'Invalid OCR service. Must be "openai" or "ollama"'
|
|
});
|
|
}
|
|
|
|
if (ocrSettings.confidence_threshold && (ocrSettings.confidence_threshold < 0 || ocrSettings.confidence_threshold > 100)) {
|
|
return res.status(400).json({
|
|
error: 'Confidence threshold must be between 0 and 100'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update preferences (upsert)
|
|
const result = await client.query(`
|
|
INSERT INTO user_preferences (
|
|
user_id, default_view, items_per_page, enable_animations,
|
|
enable_ocr, theme, privacy_settings, ocr_settings, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP)
|
|
ON CONFLICT (user_id)
|
|
DO UPDATE SET
|
|
default_view = COALESCE($2, user_preferences.default_view),
|
|
items_per_page = COALESCE($3, user_preferences.items_per_page),
|
|
enable_animations = COALESCE($4, user_preferences.enable_animations),
|
|
enable_ocr = COALESCE($5, user_preferences.enable_ocr),
|
|
theme = COALESCE($6, user_preferences.theme),
|
|
privacy_settings = COALESCE($7, user_preferences.privacy_settings),
|
|
ocr_settings = COALESCE($8, user_preferences.ocr_settings),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
RETURNING *
|
|
`, [
|
|
user.userId,
|
|
defaultView,
|
|
itemsPerPage,
|
|
enableAnimations,
|
|
enableOcr,
|
|
theme,
|
|
privacySettings ? JSON.stringify(privacySettings) : null,
|
|
ocrSettings ? JSON.stringify(ocrSettings) : null
|
|
]);
|
|
|
|
const preferences = result.rows[0];
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Preferences updated successfully',
|
|
preferences: {
|
|
defaultView: preferences.default_view,
|
|
itemsPerPage: preferences.items_per_page,
|
|
enableAnimations: preferences.enable_animations,
|
|
enableOcr: preferences.enable_ocr,
|
|
theme: preferences.theme,
|
|
privacySettings: preferences.privacy_settings,
|
|
ocrSettings: preferences.ocr_settings,
|
|
updatedAt: preferences.updated_at
|
|
}
|
|
});
|
|
|
|
} else {
|
|
res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('User preferences error:', error);
|
|
|
|
if (error.message.includes('authorization') || error.message.includes('token')) {
|
|
res.status(401).json({ error: 'Unauthorized' });
|
|
} else {
|
|
res.status(500).json({
|
|
error: 'Failed to manage preferences',
|
|
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
});
|
|
}
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|