Added more listing routes for media/scores/counters
This commit is contained in:
parent
96f0f0a76b
commit
36c94a90df
|
|
@ -82,26 +82,159 @@ router.get("/:id", async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Update listing by ID
|
||||
// Update listing (and optionally its animal) by ID
|
||||
router.put("/:id", async (req, res) => {
|
||||
const listingId = req.params.id;
|
||||
const { title, description, price } = req.body;
|
||||
try {
|
||||
const updateResult = await pool.query(
|
||||
"UPDATE listings SET title = $1, description = $2, price = $3, updated_at = NOW() WHERE id = $4 RETURNING *",
|
||||
[title, description, price, listingId]
|
||||
);
|
||||
if (updateResult.rows.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: "Listing not found for update" });
|
||||
}
|
||||
res.status(200).json(updateResult.rows[0]);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: `Internal Server Error in updating the specified ${listingId} listing`,
|
||||
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
|
||||
|
|
@ -313,6 +446,155 @@ router.get("/user/:user_id", async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
|
||||
try {
|
||||
const result = await pool.query(query, values);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Listing not found" });
|
||||
}
|
||||
res.status(200).json({
|
||||
message:
|
||||
"Listing listing_score and/or listing_score_status updated",
|
||||
listing: result.rows[0],
|
||||
});
|
||||
} 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
|
||||
router.patch("/:id/counter", 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 {
|
||||
const updateResult = await pool.query(
|
||||
`
|
||||
UPDATE listings
|
||||
SET ${type} = COALESCE(${type}, 0) + $1, updated_at = NOW()
|
||||
WHERE id = $2
|
||||
RETURNING *;
|
||||
`,
|
||||
[increment, listingId]
|
||||
);
|
||||
|
||||
if (updateResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: "Listing not found" });
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
message: `Listing ${type} incremented by ${increment}`,
|
||||
listing: updateResult.rows[0],
|
||||
});
|
||||
} 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
|
||||
router.patch("/:id/status", async (req, res) => {
|
||||
const listingId = req.params.id;
|
||||
|
|
|
|||
Loading…
Reference in New Issue