Updated location and listing routes acoording to new db schema
This commit is contained in:
parent
662ce498d8
commit
05d1dfe718
|
|
@ -3,387 +3,12 @@ import pool from "../db/pool.js";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Get all listings
|
const createNewLocation = async (client, userId, locationData) => {
|
||||||
router.get("/", async (req, res) => {
|
|
||||||
const speciesId = req.query.species_id;
|
|
||||||
const breedId = req.query.breed_id;
|
|
||||||
const state = req.query.state;
|
|
||||||
const district = req.query.district;
|
|
||||||
const minPrice = req.query.min_price;
|
|
||||||
const listingType = req.query.listing_type;
|
|
||||||
|
|
||||||
let baseQuery = `
|
|
||||||
SELECT l.*, a.species_id, a.breed_id, loc.state, loc.district
|
|
||||||
FROM listings l
|
|
||||||
LEFT JOIN animals a ON a.id = l.animal_id
|
|
||||||
LEFT JOIN locations loc ON loc.id = a.location_id
|
|
||||||
WHERE 1=1
|
|
||||||
`;
|
|
||||||
const queryParams = [];
|
|
||||||
let paramIndex = 1;
|
|
||||||
|
|
||||||
if (speciesId) {
|
|
||||||
baseQuery += ` AND a.species_id = $${paramIndex}`;
|
|
||||||
queryParams.push(speciesId);
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
if (breedId) {
|
|
||||||
baseQuery += ` AND a.breed_id = $${paramIndex}`;
|
|
||||||
queryParams.push(breedId);
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
if (state) {
|
|
||||||
baseQuery += ` AND loc.state = $${paramIndex}`;
|
|
||||||
queryParams.push(state);
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
if (district) {
|
|
||||||
baseQuery += ` AND loc.district = $${paramIndex}`;
|
|
||||||
queryParams.push(district);
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
if (minPrice) {
|
|
||||||
baseQuery += ` AND l.price >= $${paramIndex}`;
|
|
||||||
queryParams.push(minPrice);
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
if (listingType) {
|
|
||||||
baseQuery += ` AND l.listing_type = $${paramIndex}`;
|
|
||||||
queryParams.push(listingType);
|
|
||||||
paramIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const listingsResult = await pool.query(baseQuery, queryParams);
|
|
||||||
res.status(200).json(listingsResult.rows);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal Server Error in fetching listings",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get listing by ID
|
|
||||||
router.get("/:id", async (req, res) => {
|
|
||||||
const listingId = req.params.id;
|
|
||||||
try {
|
|
||||||
const listingResult = await pool.query(
|
|
||||||
"SELECT * FROM listings WHERE id = $1",
|
|
||||||
[listingId]
|
|
||||||
);
|
|
||||||
if (listingResult.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: "Listing not found" });
|
|
||||||
}
|
|
||||||
res.status(200).json(listingResult.rows[0]);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: `Internal Server Error in fetching the specified ${listingId} listing`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update listing (and optionally its animal) by ID
|
|
||||||
router.put("/:id", async (req, res) => {
|
|
||||||
const listingId = req.params.id;
|
|
||||||
const { title, price, currency, is_negotiable, listing_type } = req.body;
|
|
||||||
const { animal } = req.body;
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof title === "undefined" &&
|
|
||||||
typeof price === "undefined" &&
|
|
||||||
typeof currency === "undefined" &&
|
|
||||||
typeof is_negotiable === "undefined" &&
|
|
||||||
typeof listing_type === "undefined" &&
|
|
||||||
!animal
|
|
||||||
) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: "Nothing to update. Provide at least one listing field or an animal object.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.query("BEGIN");
|
|
||||||
|
|
||||||
// Lock listing row and get its animal_id
|
|
||||||
const listingResult = await client.query(
|
|
||||||
"SELECT * FROM listings WHERE id = $1 FOR UPDATE",
|
|
||||||
[listingId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (listingResult.rows.length === 0) {
|
|
||||||
await client.query("ROLLBACK");
|
|
||||||
return res.status(404).json({ error: "Listing not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentListing = listingResult.rows[0];
|
|
||||||
let updatedListing = currentListing;
|
|
||||||
|
|
||||||
// Build dynamic listing UPDATE if any listing fields are provided
|
|
||||||
const listingUpdates = [];
|
|
||||||
const listingValues = [];
|
|
||||||
let idx = 1;
|
|
||||||
|
|
||||||
if (typeof title !== "undefined") {
|
|
||||||
listingUpdates.push(`title = $${idx++}`);
|
|
||||||
listingValues.push(title);
|
|
||||||
}
|
|
||||||
if (typeof price !== "undefined") {
|
|
||||||
listingUpdates.push(`price = $${idx++}`);
|
|
||||||
listingValues.push(price);
|
|
||||||
}
|
|
||||||
if (typeof currency !== "undefined") {
|
|
||||||
listingUpdates.push(`currency = $${idx++}`);
|
|
||||||
listingValues.push(currency);
|
|
||||||
}
|
|
||||||
if (typeof is_negotiable !== "undefined") {
|
|
||||||
listingUpdates.push(`is_negotiable = $${idx++}`);
|
|
||||||
listingValues.push(is_negotiable);
|
|
||||||
}
|
|
||||||
if (typeof listing_type !== "undefined") {
|
|
||||||
listingUpdates.push(`listing_type = $${idx++}`);
|
|
||||||
listingValues.push(listing_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listingUpdates.length > 0) {
|
|
||||||
listingUpdates.push("updated_at = NOW()");
|
|
||||||
const listingUpdateQuery = `
|
|
||||||
UPDATE listings
|
|
||||||
SET ${listingUpdates.join(", ")}
|
|
||||||
WHERE id = $${idx}
|
|
||||||
RETURNING *;
|
|
||||||
`;
|
|
||||||
listingValues.push(listingId);
|
|
||||||
|
|
||||||
const updateResult = await client.query(
|
|
||||||
listingUpdateQuery,
|
|
||||||
listingValues
|
|
||||||
);
|
|
||||||
updatedListing = updateResult.rows[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
let updatedAnimal = null;
|
|
||||||
|
|
||||||
// Update the linked animal if payload is provided
|
|
||||||
if (animal && updatedListing.animal_id) {
|
|
||||||
const animalUpdates = [];
|
|
||||||
const animalValues = [];
|
|
||||||
let aIdx = 1;
|
|
||||||
|
|
||||||
const updatableAnimalFields = [
|
|
||||||
"species_id",
|
|
||||||
"breed_id",
|
|
||||||
"sex",
|
|
||||||
"age_months",
|
|
||||||
"weight_kg",
|
|
||||||
"color_markings",
|
|
||||||
"quantity",
|
|
||||||
"purpose",
|
|
||||||
"health_status",
|
|
||||||
"vaccinated",
|
|
||||||
"dewormed",
|
|
||||||
"previous_pregnancies_count",
|
|
||||||
"pregnancy_status",
|
|
||||||
"milk_yield_litre_per_day",
|
|
||||||
"ear_tag_no",
|
|
||||||
"description",
|
|
||||||
"suggested_care",
|
|
||||||
"location_id",
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const field of updatableAnimalFields) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(animal, field)) {
|
|
||||||
animalUpdates.push(`${field} = $${aIdx++}`);
|
|
||||||
animalValues.push(animal[field]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (animalUpdates.length > 0) {
|
|
||||||
animalUpdates.push("updated_at = NOW()");
|
|
||||||
const animalUpdateQuery = `
|
|
||||||
UPDATE animals
|
|
||||||
SET ${animalUpdates.join(", ")}
|
|
||||||
WHERE id = $${aIdx}
|
|
||||||
RETURNING *;
|
|
||||||
`;
|
|
||||||
animalValues.push(updatedListing.animal_id);
|
|
||||||
|
|
||||||
const animalResult = await client.query(
|
|
||||||
animalUpdateQuery,
|
|
||||||
animalValues
|
|
||||||
);
|
|
||||||
|
|
||||||
if (animalResult.rows.length > 0) {
|
|
||||||
updatedAnimal = animalResult.rows[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query("COMMIT");
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
message: "Listing (and animal, if provided) updated",
|
|
||||||
listing: updatedListing,
|
|
||||||
animal: updatedAnimal,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
await client.query("ROLLBACK");
|
|
||||||
return res.status(500).json({
|
|
||||||
error: `Internal Server Error updating listing ${listingId}: ${error.message}`,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit a new listing
|
|
||||||
router.post("/", async (req, res) => {
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query("BEGIN");
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
seller_id,
|
lat,
|
||||||
title,
|
lng,
|
||||||
price,
|
source_type,
|
||||||
currency,
|
// Location Details
|
||||||
is_negotiable,
|
|
||||||
listing_type,
|
|
||||||
animal,
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
// Add a new location if provided
|
|
||||||
if (!animal.location_id && animal.new_location) {
|
|
||||||
const newLocation = {
|
|
||||||
...animal.new_location,
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
newLocation.is_saved_address &&
|
|
||||||
!newLocation.user_id &&
|
|
||||||
seller_id
|
|
||||||
) {
|
|
||||||
newLocation.user_id = seller_id;
|
|
||||||
}
|
|
||||||
animal.location_id = await addNewLocation(client, newLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
const animalId = await addAnimalToListing(client, animal);
|
|
||||||
|
|
||||||
const listingInsertQuery =
|
|
||||||
"INSERT INTO listings (seller_id, animal_id, title, price, currency, is_negotiable, listing_type, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW()) RETURNING *";
|
|
||||||
const listingValues = [
|
|
||||||
seller_id,
|
|
||||||
animalId,
|
|
||||||
title,
|
|
||||||
price,
|
|
||||||
currency,
|
|
||||||
is_negotiable,
|
|
||||||
listing_type,
|
|
||||||
];
|
|
||||||
const listingResult = await client.query(
|
|
||||||
listingInsertQuery,
|
|
||||||
listingValues
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.body.media &&
|
|
||||||
Array.isArray(req.body.media) &&
|
|
||||||
req.body.media.length > 0
|
|
||||||
) {
|
|
||||||
const listingId = listingResult.rows[0].id;
|
|
||||||
const mediaInsertQuery = `
|
|
||||||
INSERT INTO listing_media (listing_id, media_url, media_type, is_primary, sort_order, created_at, updated_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
|
|
||||||
RETURNING *;
|
|
||||||
`;
|
|
||||||
for (let i = 0; i < req.body.media.length; i++) {
|
|
||||||
const media = req.body.media[i];
|
|
||||||
await client.query(mediaInsertQuery, [
|
|
||||||
listingId,
|
|
||||||
media.media_url,
|
|
||||||
media.media_type,
|
|
||||||
typeof media.is_primary !== "undefined"
|
|
||||||
? media.is_primary
|
|
||||||
: false,
|
|
||||||
typeof media.sort_order !== "undefined"
|
|
||||||
? media.sort_order
|
|
||||||
: i + 1,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query("COMMIT");
|
|
||||||
res.status(201).json(listingResult.rows[0]);
|
|
||||||
} catch (error) {
|
|
||||||
await client.query("ROLLBACK");
|
|
||||||
res.status(500).json({
|
|
||||||
error:
|
|
||||||
"Internal Server Error in creating new listing: " +
|
|
||||||
error.message,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const addAnimalToListing = async (client, animal) => {
|
|
||||||
try {
|
|
||||||
const animalInsertQuery =
|
|
||||||
"INSERT INTO animals (species_id, breed_id, sex, age_months, weight_kg, color_markings, quantity, purpose, health_status, vaccinated, dewormed, previous_pregnancies_count, pregnancy_status, milk_yield_litre_per_day, ear_tag_no, description, suggested_care, location_id, created_from, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, NOW(), NOW()) RETURNING id";
|
|
||||||
const animalValues = [
|
|
||||||
animal.species_id,
|
|
||||||
animal.breed_id,
|
|
||||||
animal.sex,
|
|
||||||
animal.age_months,
|
|
||||||
animal.weight_kg,
|
|
||||||
animal.color_markings,
|
|
||||||
animal.quantity,
|
|
||||||
animal.purpose,
|
|
||||||
animal.health_status,
|
|
||||||
animal.vaccinated,
|
|
||||||
animal.dewormed,
|
|
||||||
animal.previous_pregnancies_count,
|
|
||||||
animal.pregnancy_status,
|
|
||||||
animal.milk_yield_litre_per_day,
|
|
||||||
animal.ear_tag_no,
|
|
||||||
animal.description,
|
|
||||||
animal.suggested_care,
|
|
||||||
animal.location_id,
|
|
||||||
animal.created_from || "listing",
|
|
||||||
];
|
|
||||||
const animalResult = await client.query(
|
|
||||||
animalInsertQuery,
|
|
||||||
animalValues
|
|
||||||
);
|
|
||||||
const animalId = animalResult.rows[0].id;
|
|
||||||
return animalId;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error("Error adding animal to listing: " + error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addNewLocation = async (client, location) => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
user_id = null,
|
|
||||||
is_saved_address = false,
|
|
||||||
location_type = null,
|
|
||||||
country = null,
|
|
||||||
state = null,
|
|
||||||
district = null,
|
|
||||||
city_village = null,
|
|
||||||
pincode = null,
|
|
||||||
lat = null,
|
|
||||||
lng = null,
|
|
||||||
source_type = "unknown",
|
|
||||||
source_confidence = "medium",
|
|
||||||
} = location;
|
|
||||||
|
|
||||||
const locationInsertQuery =
|
|
||||||
"INSERT INTO locations (user_id, is_saved_address, location_type, country, state, district, city_village, pincode, lat, lng, source_type, source_confidence, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NOW(), NOW()) RETURNING id";
|
|
||||||
const locationValues = [
|
|
||||||
user_id,
|
|
||||||
is_saved_address,
|
is_saved_address,
|
||||||
location_type,
|
location_type,
|
||||||
country,
|
country,
|
||||||
|
|
@ -391,235 +16,384 @@ const addNewLocation = async (client, location) => {
|
||||||
district,
|
district,
|
||||||
city_village,
|
city_village,
|
||||||
pincode,
|
pincode,
|
||||||
lat,
|
} = locationData;
|
||||||
lng,
|
|
||||||
source_type,
|
// 1a. Insert into locations
|
||||||
source_confidence,
|
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,
|
||||||
|
country, state, district, city_village, pincode
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
|
`;
|
||||||
|
const detailsValues = [
|
||||||
|
locationId,
|
||||||
|
is_saved_address || false,
|
||||||
|
location_type || "other",
|
||||||
|
country,
|
||||||
|
state,
|
||||||
|
district,
|
||||||
|
city_village,
|
||||||
|
pincode,
|
||||||
];
|
];
|
||||||
const locationResult = await client.query(
|
await client.query(insertLocationDetailsQuery, detailsValues);
|
||||||
locationInsertQuery,
|
|
||||||
locationValues
|
return locationId;
|
||||||
);
|
|
||||||
return locationResult.rows[0].id;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error("Error adding new location: " + error.message);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete listing by ID
|
// 1. GET / (Main Feed) - Optimized with idx_listings_feed_optimized
|
||||||
router.delete("/:id", async (req, res) => {
|
router.get("/", async (req, res) => {
|
||||||
const listingId = req.params.id;
|
|
||||||
try {
|
try {
|
||||||
const deleteResult = await pool.query(
|
const { status = "active", species_id, price_min, price_max, limit = 20, offset = 0 } = req.query;
|
||||||
"DELETE FROM listings WHERE id = $1 RETURNING *",
|
|
||||||
[listingId]
|
|
||||||
);
|
|
||||||
if (deleteResult.rows.length === 0) {
|
|
||||||
return res
|
|
||||||
.status(404)
|
|
||||||
.json({ error: "Listing not found or already deleted" });
|
|
||||||
}
|
|
||||||
res.status(200).json({
|
|
||||||
message: "Listing deleted successfully",
|
|
||||||
listing: deleteResult.rows[0],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: `Internal Server Error in deleting the specified ${listingId} listing`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get all listings for a user
|
let queryText = `
|
||||||
router.get("/user/:user_id", async (req, res) => {
|
SELECT * FROM listings
|
||||||
const userId = req.params.user_id;
|
WHERE deleted = FALSE
|
||||||
try {
|
AND status = $1
|
||||||
const listingsResult = await pool.query(
|
|
||||||
"SELECT * FROM listings WHERE seller_id = $1 ORDER BY created_at DESC",
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
res.status(200).json(listingsResult.rows);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal Server Error in fetching user listings",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add media to listing
|
|
||||||
router.post("/:id/media", async (req, res) => {
|
|
||||||
const listingId = req.params.id;
|
|
||||||
const { media } = req.body;
|
|
||||||
|
|
||||||
if (!media || (Array.isArray(media) && media.length === 0)) {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.json({ error: "media array is required and cannot be empty" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = Array.isArray(media) ? media : [media];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const inserted = [];
|
|
||||||
const insertSql =
|
|
||||||
"INSERT INTO listing_media (listing_id, media_url, media_type, is_primary, sort_order, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, NOW(), NOW()) RETURNING *";
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const item = items[i];
|
|
||||||
const result = await pool.query(insertSql, [
|
|
||||||
listingId,
|
|
||||||
item.media_url,
|
|
||||||
item.media_type,
|
|
||||||
typeof item.is_primary !== "undefined"
|
|
||||||
? item.is_primary
|
|
||||||
: false,
|
|
||||||
typeof item.sort_order !== "undefined"
|
|
||||||
? item.sort_order
|
|
||||||
: i + 1,
|
|
||||||
]);
|
|
||||||
inserted.push(result.rows[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json(inserted);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: `Internal Server Error adding media to listing ${listingId}: ${error.message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update listing score and score status
|
|
||||||
router.patch("/:id/score", async (req, res) => {
|
|
||||||
const listingId = req.params.id;
|
|
||||||
const { listing_score, listing_score_status } = req.body;
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof listing_score === "undefined" &&
|
|
||||||
typeof listing_score_status === "undefined"
|
|
||||||
) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: "At least one of listing_score or listing_score_status must be provided",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const updates = [];
|
|
||||||
const values = [];
|
|
||||||
let idx = 1;
|
|
||||||
|
|
||||||
if (typeof listing_score !== "undefined") {
|
|
||||||
updates.push(`listing_score = $${idx++}`);
|
|
||||||
values.push(listing_score);
|
|
||||||
}
|
|
||||||
if (typeof listing_score_status !== "undefined") {
|
|
||||||
updates.push(`listing_score_status = $${idx++}`);
|
|
||||||
values.push(listing_score_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.push(`updated_at = NOW()`);
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
UPDATE listings
|
|
||||||
SET ${updates.join(", ")}
|
|
||||||
WHERE id = $${idx}
|
|
||||||
RETURNING *;
|
|
||||||
`;
|
`;
|
||||||
values.push(listingId);
|
const queryParams = [status];
|
||||||
|
let paramCount = 1;
|
||||||
|
|
||||||
|
if (species_id) {
|
||||||
|
paramCount++;
|
||||||
|
queryText += ` AND filter_species_id = $${paramCount}`;
|
||||||
|
queryParams.push(species_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (price_min) {
|
||||||
|
paramCount++;
|
||||||
|
queryText += ` AND price >= $${paramCount}`;
|
||||||
|
queryParams.push(price_min);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (price_max) {
|
||||||
|
paramCount++;
|
||||||
|
queryText += ` AND price <= $${paramCount}`;
|
||||||
|
queryParams.push(price_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryText += ` ORDER BY created_at DESC LIMIT $${paramCount + 1} OFFSET $${paramCount + 2}`;
|
||||||
|
queryParams.push(limit, offset);
|
||||||
|
|
||||||
|
const result = await pool.query(queryText, queryParams);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching listings feed:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. GET /near-me (Spatial Search) - Optimized with idx_listings_spatial
|
||||||
|
router.get("/near-me", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(query, values);
|
const { lat, lng, radius_meters = 50000, limit = 20, offset = 0 } = req.query;
|
||||||
|
|
||||||
|
if (!lat || !lng) {
|
||||||
|
return res.status(400).json({ error: "Latitude and Longitude are required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryText = `
|
||||||
|
SELECT * FROM listings
|
||||||
|
WHERE deleted = FALSE
|
||||||
|
AND ST_DWithin(filter_location_geog, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography, $3)
|
||||||
|
ORDER BY filter_location_geog <-> ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography
|
||||||
|
LIMIT $4 OFFSET $5
|
||||||
|
`;
|
||||||
|
const queryParams = [lng, lat, radius_meters, limit, offset]; // Note: PostGIS uses (lng, lat) for Points
|
||||||
|
|
||||||
|
const result = await pool.query(queryText, queryParams);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching nearby listings:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. GET /search TODO: (Full Text Search) - Optimized with idx_listings_search_gin
|
||||||
|
router.get("/search", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { q, limit = 20, offset = 0 } = req.query;
|
||||||
|
|
||||||
|
if (!q) {
|
||||||
|
return res.status(400).json({ error: "Search query 'q' is required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryText = `
|
||||||
|
SELECT * FROM listings
|
||||||
|
WHERE deleted = FALSE
|
||||||
|
AND search_vector @@ plainto_tsquery('english', $1)
|
||||||
|
LIMIT $2 OFFSET $3
|
||||||
|
`;
|
||||||
|
const queryParams = [q, limit, offset];
|
||||||
|
|
||||||
|
const result = await pool.query(queryText, queryParams);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error searching listings:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. GET /seller/:sellerId (My Listings) - Optimized with idx_listings_seller_status
|
||||||
|
router.get("/seller/:sellerId", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { sellerId } = req.params;
|
||||||
|
const { status } = req.query;
|
||||||
|
|
||||||
|
let queryText = `
|
||||||
|
SELECT * FROM listings
|
||||||
|
WHERE deleted = FALSE
|
||||||
|
AND seller_id = $1
|
||||||
|
`;
|
||||||
|
const queryParams = [sellerId];
|
||||||
|
let paramCount = 1;
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
paramCount++;
|
||||||
|
queryText += ` AND status = $${paramCount}`;
|
||||||
|
queryParams.push(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryText += ` ORDER BY created_at DESC`;
|
||||||
|
|
||||||
|
const result = await pool.query(queryText, queryParams);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching seller listings:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. CRUD Operations
|
||||||
|
|
||||||
|
// CREATE Listing
|
||||||
|
router.post("/", async (req, res) => {
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
|
||||||
|
const {
|
||||||
|
// Listing details
|
||||||
|
seller_id,
|
||||||
|
title,
|
||||||
|
price,
|
||||||
|
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,
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
let final_location_id = location_id;
|
||||||
|
|
||||||
|
// 1. Create Location (if needed)
|
||||||
|
if (!final_location_id && new_location) {
|
||||||
|
final_location_id = await createNewLocation(client, seller_id, new_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create Animal
|
||||||
|
const insertAnimalQuery = `
|
||||||
|
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
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||||
|
RETURNING id
|
||||||
|
`;
|
||||||
|
const animalValues = [
|
||||||
|
species_id,
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
|
||||||
|
const animalResult = await client.query(insertAnimalQuery, animalValues);
|
||||||
|
const animal_id = animalResult.rows[0].id;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
RETURNING *
|
||||||
|
`;
|
||||||
|
const listingValues = [
|
||||||
|
seller_id,
|
||||||
|
animal_id,
|
||||||
|
title,
|
||||||
|
price,
|
||||||
|
currency,
|
||||||
|
is_negotiable,
|
||||||
|
listing_type,
|
||||||
|
status,
|
||||||
|
];
|
||||||
|
|
||||||
|
const listingResult = await client.query(insertListingQuery, listingValues);
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
|
||||||
|
res.status(201).json(listingResult.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
console.error("Error creating listing with animal:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET Single Listing
|
||||||
|
router.get("/:id", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = 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.id = $1 AND l.deleted = FALSE
|
||||||
|
`;
|
||||||
|
const result = await pool.query(queryText, [id]);
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
if (result.rows.length === 0) {
|
||||||
return res.status(404).json({ error: "Listing not found" });
|
return res.status(404).json({ error: "Listing not found" });
|
||||||
}
|
}
|
||||||
res.status(200).json({
|
|
||||||
message:
|
res.json(result.rows[0]);
|
||||||
"Listing listing_score and/or listing_score_status updated",
|
} catch (err) {
|
||||||
listing: result.rows[0],
|
console.error("Error fetching listing:", err);
|
||||||
});
|
res.status(500).json({ error: "Internal server error" });
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: `Internal Server Error updating listing_score/listing_score_status for listing ${listingId}: ${error.message}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update listing counters (views, bookmarks, enquiries, clicks) by incrementing
|
// UPDATE Listing
|
||||||
router.patch("/:id/counter", async (req, res) => {
|
router.put("/:id", async (req, res) => {
|
||||||
const listingId = req.params.id;
|
|
||||||
const { type, increment = 1 } = req.body;
|
|
||||||
|
|
||||||
const validTypes = [
|
|
||||||
"views_count",
|
|
||||||
"bookmarks_count",
|
|
||||||
"enquiries_call_count",
|
|
||||||
"enquiries_whatsapp_count",
|
|
||||||
"clicks_count",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!type || !validTypes.includes(type)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error:
|
|
||||||
"Invalid or missing type. Must be one of: " +
|
|
||||||
validTypes.join(", "),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof increment !== "number") {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: "Increment must be a number",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updateResult = await pool.query(
|
const { id } = req.params;
|
||||||
`
|
const { title, price, currency, is_negotiable, listing_type, status } = req.body;
|
||||||
UPDATE listings
|
|
||||||
SET ${type} = COALESCE(${type}, 0) + $1, updated_at = NOW()
|
|
||||||
WHERE id = $2
|
|
||||||
RETURNING *;
|
|
||||||
`,
|
|
||||||
[increment, listingId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateResult.rows.length === 0) {
|
const queryText = `
|
||||||
|
UPDATE listings
|
||||||
|
SET title = COALESCE($1, title),
|
||||||
|
price = COALESCE($2, price),
|
||||||
|
currency = COALESCE($3, currency),
|
||||||
|
is_negotiable = COALESCE($4, is_negotiable),
|
||||||
|
listing_type = COALESCE($5, listing_type),
|
||||||
|
status = COALESCE($6, status)
|
||||||
|
WHERE id = $7 AND deleted = FALSE
|
||||||
|
RETURNING *
|
||||||
|
`;
|
||||||
|
const queryParams = [title, price, currency, is_negotiable, listing_type, status, id];
|
||||||
|
|
||||||
|
const result = await pool.query(queryText, queryParams);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
return res.status(404).json({ error: "Listing not found" });
|
return res.status(404).json({ error: "Listing not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
res.json(result.rows[0]);
|
||||||
message: `Listing ${type} incremented by ${increment}`,
|
} catch (err) {
|
||||||
listing: updateResult.rows[0],
|
console.error("Error updating listing:", err);
|
||||||
});
|
res.status(500).json({ error: "Internal server error" });
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: `Internal Server Error incrementing ${type} for listing ${listingId}: ${error.message}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark listing as sold or update listing status
|
// SOFT DELETE Listing
|
||||||
router.patch("/:id/status", async (req, res) => {
|
router.delete("/:id", async (req, res) => {
|
||||||
const listingId = req.params.id;
|
|
||||||
const { status } = req.body;
|
|
||||||
if (!status) {
|
|
||||||
return res.status(400).json({ error: "Status must be provided" });
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const updateResult = await pool.query(
|
const { id } = req.params;
|
||||||
"UPDATE listings SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING *",
|
const { deleted_reason } = req.body;
|
||||||
[status, listingId]
|
|
||||||
);
|
const queryText = `
|
||||||
if (updateResult.rows.length === 0) {
|
UPDATE listings
|
||||||
return res
|
SET deleted = TRUE, deleted_reason = $1
|
||||||
.status(404)
|
WHERE id = $2
|
||||||
.json({ error: "Listing not found for status update" });
|
RETURNING *
|
||||||
|
`;
|
||||||
|
const result = await pool.query(queryText, [deleted_reason, id]);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
return res.status(404).json({ error: "Listing not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ message: "Listing deleted successfully", listing: result.rows[0] });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error deleting listing:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
}
|
}
|
||||||
res.status(200).json({
|
|
||||||
message: "Listing status updated",
|
|
||||||
listing: updateResult.rows[0],
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: `Internal Server Error updating listing status for ${listingId}`,
|
// Get the list of breeds
|
||||||
|
router.get("/breeds", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const queryText = "SELECT * FROM breeds";
|
||||||
|
const result = await pool.query(queryText);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching breeds:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Get user favorite listings
|
||||||
|
router.get("/user/:userId/favorites", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { userId } = req.params;
|
||||||
|
const queryText = `
|
||||||
|
SELECT l.*, row_to_json(a) as animal
|
||||||
|
FROM favorites f
|
||||||
|
JOIN listings l ON f.listing_id = l.id
|
||||||
|
JOIN animals a ON l.animal_id = a.id
|
||||||
|
WHERE f.user_id = $1 AND f.deleted = FALSE AND l.deleted = FALSE
|
||||||
|
ORDER BY f.created_at DESC
|
||||||
|
`;
|
||||||
|
const result = await pool.query(queryText, [userId]);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching favorite listings:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,18 @@ import pool from "../db/pool.js";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Add a new location
|
// 1. CREATE Location
|
||||||
router.post("/", async (req, res) => {
|
router.post("/", async (req, res) => {
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
user_id,
|
user_id,
|
||||||
|
lat,
|
||||||
|
lng,
|
||||||
|
source_type,
|
||||||
|
// Location Details
|
||||||
is_saved_address,
|
is_saved_address,
|
||||||
location_type,
|
location_type,
|
||||||
country,
|
country,
|
||||||
|
|
@ -14,48 +22,106 @@ router.post("/", async (req, res) => {
|
||||||
district,
|
district,
|
||||||
city_village,
|
city_village,
|
||||||
pincode,
|
pincode,
|
||||||
lat,
|
|
||||||
lng,
|
|
||||||
source_type,
|
|
||||||
source_confidence,
|
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
try {
|
// 1. Insert into locations
|
||||||
const insertQuery =
|
const insertLocationQuery = `
|
||||||
"INSERT INTO locations (user_id, is_saved_address, location_type, country, state, district, city_village, pincode, lat, lng, source_type, source_confidence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *";
|
INSERT INTO locations (user_id, lat, lng, source_type)
|
||||||
const values = [
|
VALUES ($1, $2, $3, $4)
|
||||||
user_id,
|
RETURNING id, user_id, lat, lng, source_type, created_at
|
||||||
is_saved_address,
|
`;
|
||||||
location_type,
|
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)
|
||||||
|
RETURNING *
|
||||||
|
`;
|
||||||
|
const detailsValues = [
|
||||||
|
location.id,
|
||||||
|
is_saved_address || false,
|
||||||
|
location_type || "other",
|
||||||
country,
|
country,
|
||||||
state,
|
state,
|
||||||
district,
|
district,
|
||||||
city_village,
|
city_village,
|
||||||
pincode,
|
pincode,
|
||||||
lat,
|
|
||||||
lng,
|
|
||||||
source_type,
|
|
||||||
source_confidence,
|
|
||||||
];
|
];
|
||||||
|
const detailsResult = await client.query(insertDetailsQuery, detailsValues);
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
|
||||||
const result = await pool.query(insertQuery, values);
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
message: "Location added successfully",
|
...location,
|
||||||
location: result.rows[0],
|
details: detailsResult.rows[0],
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error adding location:", error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal server error adding location",
|
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
console.error("Error creating location:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update a location by ID
|
// 2. GET User Locations
|
||||||
|
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
|
||||||
|
`;
|
||||||
|
const result = await pool.query(queryText, [userId]);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching user locations:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. GET Single Location
|
||||||
|
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
|
||||||
|
`;
|
||||||
|
const result = await pool.query(queryText, [id]);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
return res.status(404).json({ error: "Location not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(result.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching location:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. UPDATE Location
|
||||||
router.put("/:id", async (req, res) => {
|
router.put("/:id", async (req, res) => {
|
||||||
const locationId = req.params.id;
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
const {
|
const {
|
||||||
user_id,
|
lat,
|
||||||
|
lng,
|
||||||
|
source_type,
|
||||||
|
// Location Details
|
||||||
is_saved_address,
|
is_saved_address,
|
||||||
location_type,
|
location_type,
|
||||||
country,
|
country,
|
||||||
|
|
@ -63,17 +129,42 @@ router.put("/:id", async (req, res) => {
|
||||||
district,
|
district,
|
||||||
city_village,
|
city_village,
|
||||||
pincode,
|
pincode,
|
||||||
lat,
|
|
||||||
lng,
|
|
||||||
source_type,
|
|
||||||
source_confidence,
|
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
try {
|
await client.query("BEGIN");
|
||||||
const updateQuery =
|
|
||||||
"UPDATE locations SET user_id = $1, is_saved_address = $2, location_type = $3, country = $4, state = $5, district = $6, city_village = $7, pincode = $8, lat = $9, lng = $10, source_type = $11, source_confidence = $12 WHERE id = $13 RETURNING *";
|
// 1. Update locations
|
||||||
const values = [
|
const updateLocationQuery = `
|
||||||
user_id,
|
UPDATE locations
|
||||||
|
SET lat = COALESCE($1, lat),
|
||||||
|
lng = COALESCE($2, lng),
|
||||||
|
source_type = COALESCE($3, source_type)
|
||||||
|
WHERE id = $4 AND deleted = FALSE
|
||||||
|
RETURNING *
|
||||||
|
`;
|
||||||
|
const locationResult = await client.query(updateLocationQuery, [lat, lng, source_type, id]);
|
||||||
|
|
||||||
|
if (locationResult.rows.length === 0) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
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,
|
is_saved_address,
|
||||||
location_type,
|
location_type,
|
||||||
country,
|
country,
|
||||||
|
|
@ -81,119 +172,49 @@ router.put("/:id", async (req, res) => {
|
||||||
district,
|
district,
|
||||||
city_village,
|
city_village,
|
||||||
pincode,
|
pincode,
|
||||||
lat,
|
id,
|
||||||
lng,
|
]);
|
||||||
source_type,
|
|
||||||
source_confidence,
|
|
||||||
locationId,
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = await pool.query(updateQuery, values);
|
await client.query("COMMIT");
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: "Location not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.json({
|
||||||
message: "Location updated successfully",
|
...locationResult.rows[0],
|
||||||
location: result.rows[0],
|
details: detailsResult.rows[0] || null,
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating location:", error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal server error updating location",
|
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
console.error("Error updating location:", err);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all locations for a user
|
// 5. DELETE Location
|
||||||
router.get("/user/:user_id", async (req, res) => {
|
|
||||||
const userId = req.params.user_id;
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
"SELECT * FROM locations WHERE user_id = $1 ORDER BY created_at DESC",
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
res.status(200).json(result.rows);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching user's locations:", error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal server error fetching user's locations",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Remove this route later
|
|
||||||
// Get all users
|
|
||||||
router.get("/users", async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
"SELECT * FROM users ORDER BY created_at DESC"
|
|
||||||
);
|
|
||||||
res.status(200).json(result.rows);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching all users:", error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal server error fetching users",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get all locations
|
|
||||||
router.get("/", async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
"SELECT * FROM locations ORDER BY created_at DESC"
|
|
||||||
);
|
|
||||||
res.status(200).json(result.rows);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching all locations:", error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal server error fetching locations",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get location by ID
|
|
||||||
router.get("/:id", async (req, res) => {
|
|
||||||
const locationId = req.params.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const selectQuery = "SELECT * FROM locations WHERE id = $1";
|
|
||||||
const result = await pool.query(selectQuery, [locationId]);
|
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: "Location not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json(result.rows[0]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching location:", error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: "Internal server error fetching location",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete a location by ID
|
|
||||||
router.delete("/:id", async (req, res) => {
|
router.delete("/:id", async (req, res) => {
|
||||||
const locationId = req.params.id;
|
|
||||||
try {
|
try {
|
||||||
const deleteQuery = "DELETE FROM locations WHERE id = $1 RETURNING *";
|
const { id } = req.params;
|
||||||
const result = await pool.query(deleteQuery, [locationId]);
|
const queryText = `
|
||||||
|
UPDATE locations
|
||||||
|
SET deleted = TRUE
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING id
|
||||||
|
`;
|
||||||
|
const result = await pool.query(queryText, [id]);
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
if (result.rows.length === 0) {
|
||||||
return res.status(404).json({ error: "Location not found" });
|
return res.status(404).json({ error: "Location not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(200).json({
|
// Optionally mark details as deleted if they had a deleted column, but they don't seem to based on schema view earlier?
|
||||||
message: "Location deleted successfully",
|
// Checking schema: location_details has 'deleted'.
|
||||||
location: result.rows[0],
|
|
||||||
});
|
await pool.query("UPDATE location_details SET deleted = TRUE WHERE location_id = $1", [id]);
|
||||||
} catch (error) {
|
|
||||||
console.error("Error deleting location:", error);
|
res.json({ message: "Location deleted successfully" });
|
||||||
res.status(500).json({
|
} catch (err) {
|
||||||
error: "Internal server error deleting location",
|
console.error("Error deleting location:", err);
|
||||||
});
|
res.status(500).json({ error: "Internal server error" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue