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,
lng,
source_type,
source_confidence,
selected_location,
// Location Details
is_saved_address,
location_type,
@ -18,37 +20,42 @@ const createNewLocation = async (client, userId, locationData) => {
pincode,
} = locationData;
// 1a. Insert into locations
// 1. Insert into locations (Merged Table)
const insertLocationQuery = `
INSERT INTO locations (user_id, lat, lng, source_type)
VALUES ($1, $2, $3, $4)
RETURNING id
`;
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,
INSERT INTO locations (
user_id, lat, lng, source_type, source_confidence, selected_location,
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 id
`;
const detailsValues = [
locationId,
is_saved_address || false,
const locationValues = [
userId,
lat,
lng,
source_type || "manual",
source_confidence || "unknown",
selected_location || false,
is_saved_address || false,
location_type || "other",
country,
state,
district,
city_village,
pincode,
];
await client.query(insertLocationDetailsQuery, detailsValues);
return locationId;
pincode
];
try {
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
@ -64,12 +71,76 @@ router.get("/", async (req, res) => {
const queryParams = [status];
let paramCount = 1;
if (species_id) {
if (species_id) {
paramCount++;
queryText += ` AND filter_species_id = $${paramCount}`;
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) {
paramCount++;
queryText += ` AND price >= $${paramCount}`;
@ -190,33 +261,15 @@ router.post("/", async (req, res) => {
currency,
is_negotiable,
listing_type,
status,
// Animal details
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,
animal,
media // Array of { media_url, media_type, is_primary, sort_order }
} = req.body;
let final_location_id = location_id;
let final_location_id = animal?.location_id;
// 1. Create Location (if needed)
if (!final_location_id && new_location) {
final_location_id = await createNewLocation(client, seller_id, new_location);
if (!final_location_id && animal?.new_location) {
final_location_id = await createNewLocation(client, seller_id, animal.new_location);
}
// 2. Create Animal
@ -224,28 +277,29 @@ router.post("/", async (req, res) => {
INSERT INTO animals (
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
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
`;
const animalValues = [
species_id,
breed_id,
animal.species_id,
animal.breed_id,
final_location_id,
sex,
age_months,
weight_kg,
color_markings,
quantity || 1, // Default to 1
purpose,
health_status || "healthy", // Default
vaccinated || false,
dewormed || false,
pregnancy_status || "unknown", // Default
milk_yield_litre_per_day,
ear_tag_no,
description,
animal.sex,
animal.age_months,
animal.weight_kg,
animal.color_markings,
animal.quantity || 1, // Default to 1
animal.purpose,
animal.health_status || "healthy", // Default
animal.vaccinated || false,
animal.dewormed || false,
animal.pregnancy_status || "unknown", // Default
animal.calving_number, // Added
animal.milk_yield_litre_per_day,
animal.ear_tag_no,
animal.description,
];
const animalResult = await client.query(insertAnimalQuery, animalValues);
@ -253,8 +307,8 @@ router.post("/", async (req, res) => {
// 3. Create Listing
const insertListingQuery = `
INSERT INTO listings (seller_id, animal_id, title, price, currency, is_negotiable, listing_type, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
INSERT INTO listings (seller_id, animal_id, title, price, currency, is_negotiable, listing_type)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *
`;
const listingValues = [
@ -264,11 +318,29 @@ router.post("/", async (req, res) => {
price,
currency,
is_negotiable,
listing_type,
status,
listing_type
];
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");
@ -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;

View File

@ -14,7 +14,8 @@ router.post("/", async (req, res) => {
lat,
lng,
source_type,
// Location Details
source_confidence,
selected_location,
is_saved_address,
location_type,
country,
@ -26,25 +27,20 @@ router.post("/", async (req, res) => {
// 1. Insert into locations
const insertLocationQuery = `
INSERT INTO locations (user_id, lat, lng, source_type)
VALUES ($1, $2, $3, $4)
RETURNING id, user_id, lat, lng, source_type, created_at
`;
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
INSERT INTO locations (
user_id, lat, lng, source_type, source_confidence, selected_location,
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 *
`;
const detailsValues = [
location.id,
const locationValues = [
user_id,
lat,
lng,
source_type || "manual",
source_confidence || "low",
selected_location || false,
is_saved_address || false,
location_type || "other",
country,
@ -53,13 +49,12 @@ router.post("/", async (req, res) => {
city_village,
pincode,
];
const detailsResult = await client.query(insertDetailsQuery, detailsValues);
const locationResult = await client.query(insertLocationQuery, locationValues);
await client.query("COMMIT");
res.status(201).json({
...location,
details: detailsResult.rows[0],
...locationResult.rows[0],
});
} catch (err) {
await client.query("ROLLBACK");
@ -75,11 +70,9 @@ router.get("/user/:userId", async (req, res) => {
try {
const { userId } = req.params;
const queryText = `
SELECT l.*, row_to_json(ld) as details
FROM locations l
LEFT JOIN location_details ld ON l.id = ld.location_id
WHERE l.user_id = $1 AND l.deleted = FALSE
ORDER BY l.created_at DESC
SELECT * FROM locations
WHERE user_id = $1 AND deleted = FALSE
ORDER BY created_at DESC
`;
const result = await pool.query(queryText, [userId]);
res.json(result.rows);
@ -94,10 +87,8 @@ router.get("/:id", async (req, res) => {
try {
const { id } = req.params;
const queryText = `
SELECT l.*, row_to_json(ld) as details
FROM locations l
LEFT JOIN location_details ld ON l.id = ld.location_id
WHERE l.id = $1 AND l.deleted = FALSE
SELECT * FROM locations
WHERE id = $1 AND deleted = FALSE
`;
const result = await pool.query(queryText, [id]);
@ -114,14 +105,14 @@ router.get("/:id", async (req, res) => {
// 4. UPDATE Location
router.put("/:id", async (req, res) => {
const client = await pool.connect();
try {
const { id } = req.params;
const {
lat,
lng,
source_type,
// Location Details
source_confidence,
selected_location,
is_saved_address,
location_type,
country,
@ -131,62 +122,40 @@ router.put("/:id", async (req, res) => {
pincode,
} = req.body;
await client.query("BEGIN");
// 1. Update locations
const updateLocationQuery = `
const updateQuery = `
UPDATE locations
SET lat = COALESCE($1, lat),
lng = COALESCE($2, lng),
source_type = COALESCE($3, source_type)
WHERE id = $4 AND deleted = FALSE
source_type = COALESCE($3, source_type),
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 *
`;
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) {
await client.query("ROLLBACK");
const result = await pool.query(updateQuery, values);
if (result.rows.length === 0) {
return res.status(404).json({ error: "Location not found" });
}
// 2. Update location_details
// 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,
});
res.json(result.rows[0]);
} catch (err) {
await client.query("ROLLBACK");
console.error("Error updating location:", err);
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" });
}
// 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" });
} catch (err) {
console.error("Error deleting location:", err);