Add option to not hide submitted products for product import, rework description popover, fix steps

This commit is contained in:
2026-01-29 16:03:07 -05:00
parent ee2f314775
commit f9e8c9265e
18 changed files with 842 additions and 693 deletions

View File

@@ -70,130 +70,8 @@ router.get('/:id', async (req, res) => {
}
});
// Create new named session
router.post('/', async (req, res) => {
try {
const {
user_id,
name,
current_step,
data,
product_images,
global_selections,
validation_state
} = req.body;
// Validate required fields
if (!user_id) {
return res.status(400).json({ error: 'user_id is required' });
}
if (!name || typeof name !== 'string' || name.trim().length === 0) {
return res.status(400).json({ error: 'name is required for creating a named session' });
}
if (!current_step) {
return res.status(400).json({ error: 'current_step is required' });
}
if (!data || !Array.isArray(data)) {
return res.status(400).json({ error: 'data must be an array' });
}
const pool = req.app.locals.pool;
if (!pool) {
throw new Error('Database pool not initialized');
}
const result = await pool.query(`
INSERT INTO import_sessions (
user_id,
name,
current_step,
data,
product_images,
global_selections,
validation_state
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *
`, [
user_id,
name.trim(),
current_step,
JSON.stringify(data),
product_images ? JSON.stringify(product_images) : null,
global_selections ? JSON.stringify(global_selections) : null,
validation_state ? JSON.stringify(validation_state) : null
]);
res.status(201).json(result.rows[0]);
} catch (error) {
console.error('Error creating import session:', error);
res.status(500).json({
error: 'Failed to create import session',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// Update named session by ID
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const {
current_step,
data,
product_images,
global_selections,
validation_state
} = req.body;
if (!current_step) {
return res.status(400).json({ error: 'current_step is required' });
}
if (!data || !Array.isArray(data)) {
return res.status(400).json({ error: 'data must be an array' });
}
const pool = req.app.locals.pool;
if (!pool) {
throw new Error('Database pool not initialized');
}
// Check if session exists
const checkResult = await pool.query('SELECT * FROM import_sessions WHERE id = $1', [id]);
if (checkResult.rows.length === 0) {
return res.status(404).json({ error: 'Import session not found' });
}
const result = await pool.query(`
UPDATE import_sessions
SET
current_step = $1,
data = $2,
product_images = $3,
global_selections = $4,
validation_state = $5,
updated_at = CURRENT_TIMESTAMP
WHERE id = $6
RETURNING *
`, [
current_step,
JSON.stringify(data),
product_images ? JSON.stringify(product_images) : null,
global_selections ? JSON.stringify(global_selections) : null,
validation_state ? JSON.stringify(validation_state) : null,
id
]);
res.json(result.rows[0]);
} catch (error) {
console.error('Error updating import session:', error);
res.status(500).json({
error: 'Failed to update import session',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// Autosave - upsert unnamed session for user
// IMPORTANT: This must be defined before /:id routes to avoid Express matching "autosave" as an :id
router.put('/autosave', async (req, res) => {
try {
const {
@@ -240,7 +118,7 @@ router.put('/autosave', async (req, res) => {
global_selections = EXCLUDED.global_selections,
validation_state = EXCLUDED.validation_state,
updated_at = CURRENT_TIMESTAMP
RETURNING *
RETURNING id, user_id, name, current_step, created_at, updated_at
`, [
user_id,
current_step,
@@ -260,32 +138,8 @@ router.put('/autosave', async (req, res) => {
}
});
// Delete session by ID
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
const pool = req.app.locals.pool;
if (!pool) {
throw new Error('Database pool not initialized');
}
const result = await pool.query('DELETE FROM import_sessions WHERE id = $1 RETURNING *', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Import session not found' });
}
res.json({ message: 'Import session deleted successfully' });
} catch (error) {
console.error('Error deleting import session:', error);
res.status(500).json({
error: 'Failed to delete import session',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// Delete unnamed session for user (clear autosave)
// IMPORTANT: This must be defined before /:id routes
router.delete('/autosave/:user_id', async (req, res) => {
try {
const { user_id } = req.params;
@@ -295,7 +149,7 @@ router.delete('/autosave/:user_id', async (req, res) => {
}
const result = await pool.query(
'DELETE FROM import_sessions WHERE user_id = $1 AND name IS NULL RETURNING *',
'DELETE FROM import_sessions WHERE user_id = $1 AND name IS NULL RETURNING id, user_id, name, current_step, created_at, updated_at',
[user_id]
);
@@ -313,6 +167,164 @@ router.delete('/autosave/:user_id', async (req, res) => {
}
});
// Create new named session
router.post('/', async (req, res) => {
try {
const {
user_id,
name,
current_step,
data,
product_images,
global_selections,
validation_state
} = req.body;
// Validate required fields
if (!user_id) {
return res.status(400).json({ error: 'user_id is required' });
}
if (!name || typeof name !== 'string' || name.trim().length === 0) {
return res.status(400).json({ error: 'name is required for creating a named session' });
}
if (!current_step) {
return res.status(400).json({ error: 'current_step is required' });
}
if (!data || !Array.isArray(data)) {
return res.status(400).json({ error: 'data must be an array' });
}
const pool = req.app.locals.pool;
if (!pool) {
throw new Error('Database pool not initialized');
}
const result = await pool.query(`
INSERT INTO import_sessions (
user_id,
name,
current_step,
data,
product_images,
global_selections,
validation_state
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, user_id, name, current_step, created_at, updated_at
`, [
user_id,
name.trim(),
current_step,
JSON.stringify(data),
product_images ? JSON.stringify(product_images) : null,
global_selections ? JSON.stringify(global_selections) : null,
validation_state ? JSON.stringify(validation_state) : null
]);
res.status(201).json(result.rows[0]);
} catch (error) {
console.error('Error creating import session:', error);
res.status(500).json({
error: 'Failed to create import session',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// Update named session by ID
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const {
name,
current_step,
data,
product_images,
global_selections,
validation_state
} = req.body;
if (!current_step) {
return res.status(400).json({ error: 'current_step is required' });
}
if (!data || !Array.isArray(data)) {
return res.status(400).json({ error: 'data must be an array' });
}
const pool = req.app.locals.pool;
if (!pool) {
throw new Error('Database pool not initialized');
}
// Build update query - optionally include name if provided
const hasName = name !== undefined;
const result = await pool.query(`
UPDATE import_sessions
SET
${hasName ? 'name = $1,' : ''}
current_step = $${hasName ? 2 : 1},
data = $${hasName ? 3 : 2},
product_images = $${hasName ? 4 : 3},
global_selections = $${hasName ? 5 : 4},
validation_state = $${hasName ? 6 : 5},
updated_at = CURRENT_TIMESTAMP
WHERE id = $${hasName ? 7 : 6}
RETURNING id, user_id, name, current_step, created_at, updated_at
`, hasName ? [
typeof name === 'string' ? name.trim() : name,
current_step,
JSON.stringify(data),
product_images ? JSON.stringify(product_images) : null,
global_selections ? JSON.stringify(global_selections) : null,
validation_state ? JSON.stringify(validation_state) : null,
id
] : [
current_step,
JSON.stringify(data),
product_images ? JSON.stringify(product_images) : null,
global_selections ? JSON.stringify(global_selections) : null,
validation_state ? JSON.stringify(validation_state) : null,
id
]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Import session not found' });
}
res.json(result.rows[0]);
} catch (error) {
console.error('Error updating import session:', error);
res.status(500).json({
error: 'Failed to update import session',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// Delete session by ID
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
const pool = req.app.locals.pool;
if (!pool) {
throw new Error('Database pool not initialized');
}
const result = await pool.query('DELETE FROM import_sessions WHERE id = $1 RETURNING id, user_id, name, current_step, created_at, updated_at', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Import session not found' });
}
res.json({ message: 'Import session deleted successfully' });
} catch (error) {
console.error('Error deleting import session:', error);
res.status(500).json({
error: 'Failed to delete import session',
details: error instanceof Error ? error.message : 'Unknown error'
});
}
});
// Error handling middleware
router.use((err, req, res, next) => {
console.error('Import sessions route error:', err);