Updated listing and location routes as per new DB schema
This commit is contained in:
parent
49721086f3
commit
b0e51dd6da
BIN
db/ERD.png
BIN
db/ERD.png
Binary file not shown.
|
Before Width: | Height: | Size: 551 KiB After Width: | Height: | Size: 533 KiB |
1105
db/final_db.sql
1105
db/final_db.sql
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue