Updated listing and location routes as per new DB schema

This commit is contained in:
Soham Chari 2025-12-18 13:45:07 +05:30
parent 49721086f3
commit b0e51dd6da
4 changed files with 807 additions and 675 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 KiB

After

Width:  |  Height:  |  Size: 533 KiB

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,8 @@ const createNewLocation = async (client, userId, locationData) => {
lat, lat,
lng, lng,
source_type, source_type,
source_confidence,
selected_location,
// Location Details // Location Details
is_saved_address, is_saved_address,
location_type, location_type,
@ -18,37 +20,42 @@ const createNewLocation = async (client, userId, locationData) => {
pincode, pincode,
} = locationData; } = locationData;
// 1a. Insert into locations // 1. Insert into locations (Merged Table)
const insertLocationQuery = ` const insertLocationQuery = `
INSERT INTO locations (user_id, lat, lng, source_type) INSERT INTO locations (
VALUES ($1, $2, $3, $4) user_id, lat, lng, source_type, source_confidence, selected_location,
RETURNING id is_saved_address, location_type,
`;
const locationValues = [userId, lat, lng, source_type || "manual"];
const locationResult = await client.query(insertLocationQuery, locationValues);
const locationId = locationResult.rows[0].id;
// 1b. Insert into location_details
const insertLocationDetailsQuery = `
INSERT INTO location_details (
location_id, is_saved_address, location_type,
country, state, district, city_village, pincode country, state, district, city_village, pincode
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
RETURNING id
`; `;
const detailsValues = [ const locationValues = [
locationId, userId,
is_saved_address || false, lat,
lng,
source_type || "manual",
source_confidence || "unknown",
selected_location || false,
is_saved_address || false,
location_type || "other", location_type || "other",
country, country,
state, state,
district, district,
city_village, city_village,
pincode, pincode
]; ];
await client.query(insertLocationDetailsQuery, detailsValues);
try {
return locationId; const locationResult = await client.query(insertLocationQuery, locationValues);
if (locationResult.rows.length === 0) {
throw new Error("Failed to insert location record");
}
return locationResult.rows[0].id;
} catch (error) {
console.error("Error creating new location:", error);
throw error; // Propagate error to trigger transaction rollback
}
}; };
// 1. GET / (Main Feed) - Optimized with idx_listings_feed_optimized // 1. GET / (Main Feed) - Optimized with idx_listings_feed_optimized
@ -64,12 +71,76 @@ router.get("/", async (req, res) => {
const queryParams = [status]; const queryParams = [status];
let paramCount = 1; let paramCount = 1;
if (species_id) { if (species_id) {
paramCount++; paramCount++;
queryText += ` AND filter_species_id = $${paramCount}`; queryText += ` AND filter_species_id = $${paramCount}`;
queryParams.push(species_id); queryParams.push(species_id);
} }
// New Filters
if (req.query.breed_id) {
paramCount++;
queryText += ` AND filter_breed_id = $${paramCount}`;
queryParams.push(req.query.breed_id);
}
if (req.query.sex) {
paramCount++;
queryText += ` AND filter_sex = $${paramCount}`;
queryParams.push(req.query.sex);
}
if (req.query.pregnancy_status) {
paramCount++;
queryText += ` AND filter_pregnancy_status = $${paramCount}`;
queryParams.push(req.query.pregnancy_status);
}
// Range Filters
if (req.query.age_min) {
paramCount++;
queryText += ` AND filter_age_months >= $${paramCount}`;
queryParams.push(req.query.age_min);
}
if (req.query.age_max) {
paramCount++;
queryText += ` AND filter_age_months <= $${paramCount}`;
queryParams.push(req.query.age_max);
}
if (req.query.weight_min) {
paramCount++;
queryText += ` AND filter_weight_kg >= $${paramCount}`;
queryParams.push(req.query.weight_min);
}
if (req.query.weight_max) {
paramCount++;
queryText += ` AND filter_weight_kg <= $${paramCount}`;
queryParams.push(req.query.weight_max);
}
if (req.query.calving_number_min) {
paramCount++;
queryText += ` AND filter_calving_number >= $${paramCount}`;
queryParams.push(req.query.calving_number_min);
}
if (req.query.calving_number_max) {
paramCount++;
queryText += ` AND filter_calving_number <= $${paramCount}`;
queryParams.push(req.query.calving_number_max);
}
if (req.query.milking_capacity_min) {
paramCount++;
queryText += ` AND filter_milking_capacity >= $${paramCount}`;
queryParams.push(req.query.milking_capacity_min);
}
if (req.query.milking_capacity_max) {
paramCount++;
queryText += ` AND filter_milking_capacity <= $${paramCount}`;
queryParams.push(req.query.milking_capacity_max);
}
if (price_min) { if (price_min) {
paramCount++; paramCount++;
queryText += ` AND price >= $${paramCount}`; queryText += ` AND price >= $${paramCount}`;
@ -190,33 +261,15 @@ router.post("/", async (req, res) => {
currency, currency,
is_negotiable, is_negotiable,
listing_type, listing_type,
status, animal,
// Animal details media // Array of { media_url, media_type, is_primary, sort_order }
species_id,
breed_id,
location_id,
sex,
age_months,
weight_kg,
color_markings,
quantity,
purpose,
health_status,
vaccinated,
dewormed,
pregnancy_status,
milk_yield_litre_per_day,
ear_tag_no,
description,
// New Location details
new_location,
} = req.body; } = req.body;
let final_location_id = location_id; let final_location_id = animal?.location_id;
// 1. Create Location (if needed) // 1. Create Location (if needed)
if (!final_location_id && new_location) { if (!final_location_id && animal?.new_location) {
final_location_id = await createNewLocation(client, seller_id, new_location); final_location_id = await createNewLocation(client, seller_id, animal.new_location);
} }
// 2. Create Animal // 2. Create Animal
@ -224,28 +277,29 @@ router.post("/", async (req, res) => {
INSERT INTO animals ( INSERT INTO animals (
species_id, breed_id, location_id, sex, age_months, weight_kg, species_id, breed_id, location_id, sex, age_months, weight_kg,
color_markings, quantity, purpose, health_status, vaccinated, color_markings, quantity, purpose, health_status, vaccinated,
dewormed, pregnancy_status, milk_yield_litre_per_day, ear_tag_no, description dewormed, pregnancy_status, calving_number, milk_yield_litre_per_day, ear_tag_no, description
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
RETURNING id RETURNING id
`; `;
const animalValues = [ const animalValues = [
species_id, animal.species_id,
breed_id, animal.breed_id,
final_location_id, final_location_id,
sex, animal.sex,
age_months, animal.age_months,
weight_kg, animal.weight_kg,
color_markings, animal.color_markings,
quantity || 1, // Default to 1 animal.quantity || 1, // Default to 1
purpose, animal.purpose,
health_status || "healthy", // Default animal.health_status || "healthy", // Default
vaccinated || false, animal.vaccinated || false,
dewormed || false, animal.dewormed || false,
pregnancy_status || "unknown", // Default animal.pregnancy_status || "unknown", // Default
milk_yield_litre_per_day, animal.calving_number, // Added
ear_tag_no, animal.milk_yield_litre_per_day,
description, animal.ear_tag_no,
animal.description,
]; ];
const animalResult = await client.query(insertAnimalQuery, animalValues); const animalResult = await client.query(insertAnimalQuery, animalValues);
@ -253,8 +307,8 @@ router.post("/", async (req, res) => {
// 3. Create Listing // 3. Create Listing
const insertListingQuery = ` const insertListingQuery = `
INSERT INTO listings (seller_id, animal_id, title, price, currency, is_negotiable, listing_type, status) INSERT INTO listings (seller_id, animal_id, title, price, currency, is_negotiable, listing_type)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING * RETURNING *
`; `;
const listingValues = [ const listingValues = [
@ -264,11 +318,29 @@ router.post("/", async (req, res) => {
price, price,
currency, currency,
is_negotiable, is_negotiable,
listing_type, listing_type
status,
]; ];
const listingResult = await client.query(insertListingQuery, listingValues); const listingResult = await client.query(insertListingQuery, listingValues);
const listing_id = listingResult.rows[0].id;
// 4. Create Listing Media
if (media && media.length > 0) {
const mediaInsertQuery = `
INSERT INTO listing_media (listing_id, media_url, media_type, is_primary, sort_order)
VALUES ($1, $2, $3, $4, $5)
`;
for (const item of media) {
await client.query(mediaInsertQuery, [
listing_id,
item.media_url,
item.media_type, // 'image', 'video'
item.is_primary || false,
item.sort_order || 0
]);
}
}
await client.query("COMMIT"); await client.query("COMMIT");
@ -397,4 +469,51 @@ router.get("/user/:userId/favorites", async (req, res) => {
} }
}); });
// Get listings created by user
router.get("/user/:userId", async (req, res) => {
try {
const { userId } = req.params;
const queryText = `
SELECT l.*, row_to_json(a) as animal
FROM listings l
JOIN animals a ON l.animal_id = a.id
WHERE l.deleted = FALSE AND l.seller_id = $1
ORDER BY l.created_at DESC
`;
const result = await pool.query(queryText, [userId]);
res.json(result.rows);
} catch (err) {
console.error("Error fetching user listings:", err);
res.status(500).json({ error: "Internal server error" });
}
});
// Update listing score & status
router.patch("/:id/score", async (req, res) => {
try {
const { id } = req.params;
const { listing_score, listing_score_status } = req.body;
const queryText = `
UPDATE listings
SET listing_score = COALESCE($1, listing_score),
listing_score_status = COALESCE($2, listing_score_status)
WHERE id = $3 AND deleted = FALSE
RETURNING *
`;
const queryParams = [listing_score, listing_score_status, id];
const result = await pool.query(queryText, queryParams);
if (result.rows.length === 0) {
return res.status(404).json({ error: "Listing not found" });
}
res.json(result.rows[0]);
} catch (err) {
console.error("Error updating listing:", err);
res.status(500).json({ error: "Internal server error" });
}
});
export default router; export default router;

View File

@ -14,7 +14,8 @@ router.post("/", async (req, res) => {
lat, lat,
lng, lng,
source_type, source_type,
// Location Details source_confidence,
selected_location,
is_saved_address, is_saved_address,
location_type, location_type,
country, country,
@ -26,25 +27,20 @@ router.post("/", async (req, res) => {
// 1. Insert into locations // 1. Insert into locations
const insertLocationQuery = ` const insertLocationQuery = `
INSERT INTO locations (user_id, lat, lng, source_type) INSERT INTO locations (
VALUES ($1, $2, $3, $4) user_id, lat, lng, source_type, source_confidence, selected_location,
RETURNING id, user_id, lat, lng, source_type, created_at is_saved_address, location_type, country, state, district, city_village, pincode
`;
const locationValues = [user_id, lat, lng, source_type || "manual"];
const locationResult = await client.query(insertLocationQuery, locationValues);
const location = locationResult.rows[0];
// 2. Insert into location_details
const insertDetailsQuery = `
INSERT INTO location_details (
location_id, is_saved_address, location_type,
country, state, district, city_village, pincode
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
RETURNING * RETURNING *
`; `;
const detailsValues = [ const locationValues = [
location.id, user_id,
lat,
lng,
source_type || "manual",
source_confidence || "low",
selected_location || false,
is_saved_address || false, is_saved_address || false,
location_type || "other", location_type || "other",
country, country,
@ -53,13 +49,12 @@ router.post("/", async (req, res) => {
city_village, city_village,
pincode, pincode,
]; ];
const detailsResult = await client.query(insertDetailsQuery, detailsValues); const locationResult = await client.query(insertLocationQuery, locationValues);
await client.query("COMMIT"); await client.query("COMMIT");
res.status(201).json({ res.status(201).json({
...location, ...locationResult.rows[0],
details: detailsResult.rows[0],
}); });
} catch (err) { } catch (err) {
await client.query("ROLLBACK"); await client.query("ROLLBACK");
@ -75,11 +70,9 @@ router.get("/user/:userId", async (req, res) => {
try { try {
const { userId } = req.params; const { userId } = req.params;
const queryText = ` const queryText = `
SELECT l.*, row_to_json(ld) as details SELECT * FROM locations
FROM locations l WHERE user_id = $1 AND deleted = FALSE
LEFT JOIN location_details ld ON l.id = ld.location_id ORDER BY created_at DESC
WHERE l.user_id = $1 AND l.deleted = FALSE
ORDER BY l.created_at DESC
`; `;
const result = await pool.query(queryText, [userId]); const result = await pool.query(queryText, [userId]);
res.json(result.rows); res.json(result.rows);
@ -94,10 +87,8 @@ router.get("/:id", async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const queryText = ` const queryText = `
SELECT l.*, row_to_json(ld) as details SELECT * FROM locations
FROM locations l WHERE id = $1 AND deleted = FALSE
LEFT JOIN location_details ld ON l.id = ld.location_id
WHERE l.id = $1 AND l.deleted = FALSE
`; `;
const result = await pool.query(queryText, [id]); const result = await pool.query(queryText, [id]);
@ -114,14 +105,14 @@ router.get("/:id", async (req, res) => {
// 4. UPDATE Location // 4. UPDATE Location
router.put("/:id", async (req, res) => { router.put("/:id", async (req, res) => {
const client = await pool.connect();
try { try {
const { id } = req.params; const { id } = req.params;
const { const {
lat, lat,
lng, lng,
source_type, source_type,
// Location Details source_confidence,
selected_location,
is_saved_address, is_saved_address,
location_type, location_type,
country, country,
@ -131,62 +122,40 @@ router.put("/:id", async (req, res) => {
pincode, pincode,
} = req.body; } = req.body;
await client.query("BEGIN"); const updateQuery = `
// 1. Update locations
const updateLocationQuery = `
UPDATE locations UPDATE locations
SET lat = COALESCE($1, lat), SET lat = COALESCE($1, lat),
lng = COALESCE($2, lng), lng = COALESCE($2, lng),
source_type = COALESCE($3, source_type) source_type = COALESCE($3, source_type),
WHERE id = $4 AND deleted = FALSE source_confidence = COALESCE($4, source_confidence),
selected_location = COALESCE($5, selected_location),
is_saved_address = COALESCE($6, is_saved_address),
location_type = COALESCE($7, location_type),
country = COALESCE($8, country),
state = COALESCE($9, state),
district = COALESCE($10, district),
city_village = COALESCE($11, city_village),
pincode = COALESCE($12, pincode)
WHERE id = $13 AND deleted = FALSE
RETURNING * RETURNING *
`; `;
const locationResult = await client.query(updateLocationQuery, [lat, lng, source_type, id]);
const values = [
lat, lng, source_type, source_confidence, selected_location,
is_saved_address, location_type, country, state, district, city_village, pincode,
id
];
if (locationResult.rows.length === 0) { const result = await pool.query(updateQuery, values);
await client.query("ROLLBACK");
if (result.rows.length === 0) {
return res.status(404).json({ error: "Location not found" }); return res.status(404).json({ error: "Location not found" });
} }
// 2. Update location_details res.json(result.rows[0]);
// Note: location_details might not exist depending on legacy data, so we use ON CONFLICT or just UPDATE/INSERT logic.
// For simplicity and assuming details always exist creation:
const updateDetailsQuery = `
UPDATE location_details
SET is_saved_address = COALESCE($1, is_saved_address),
location_type = COALESCE($2, location_type),
country = COALESCE($3, country),
state = COALESCE($4, state),
district = COALESCE($5, district),
city_village = COALESCE($6, city_village),
pincode = COALESCE($7, pincode)
WHERE location_id = $8
RETURNING *
`;
const detailsResult = await client.query(updateDetailsQuery, [
is_saved_address,
location_type,
country,
state,
district,
city_village,
pincode,
id,
]);
await client.query("COMMIT");
res.json({
...locationResult.rows[0],
details: detailsResult.rows[0] || null,
});
} catch (err) { } catch (err) {
await client.query("ROLLBACK");
console.error("Error updating location:", err); console.error("Error updating location:", err);
res.status(500).json({ error: "Internal server error" }); res.status(500).json({ error: "Internal server error" });
} finally {
client.release();
} }
}); });
@ -206,11 +175,6 @@ router.delete("/:id", async (req, res) => {
return res.status(404).json({ error: "Location not found" }); return res.status(404).json({ error: "Location not found" });
} }
// Optionally mark details as deleted if they had a deleted column, but they don't seem to based on schema view earlier?
// Checking schema: location_details has 'deleted'.
await pool.query("UPDATE location_details SET deleted = TRUE WHERE location_id = $1", [id]);
res.json({ message: "Location deleted successfully" }); res.json({ message: "Location deleted successfully" });
} catch (err) { } catch (err) {
console.error("Error deleting location:", err); console.error("Error deleting location:", err);