187 lines
6.1 KiB
JavaScript
187 lines
6.1 KiB
JavaScript
import express from "express";
|
||
import fetch from "node-fetch";
|
||
import bcrypt from "bcryptjs";
|
||
import { User } from "../models/User.js";
|
||
import { requireAuth } from "../middleware/auth.js";
|
||
|
||
const router = express.Router();
|
||
|
||
// GET /api/profiles/me
|
||
router.get("/me", requireAuth, async (req, res) => {
|
||
const user = await User.findById(req.user.id).select("-password -__v");
|
||
if (!user) return res.status(404).json({ error: "User not found" });
|
||
res.json({
|
||
email: user.email,
|
||
role: user.role,
|
||
profile: user.profile,
|
||
});
|
||
});
|
||
|
||
// NEW: GET any profile by its ID
|
||
router.get("/:id", async (req, res) => {
|
||
try {
|
||
const user = await User.findById(req.params.id).select("-password -__v");
|
||
if (!user) return res.status(404).json({ error: "Profile not found" });
|
||
return res.json({
|
||
email: user.email,
|
||
role: user.role,
|
||
profile: user.profile,
|
||
});
|
||
} catch (err) {
|
||
return res.status(500).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
// POST /api/profiles (create or update profile)
|
||
// – normal user: only name/address/password
|
||
// – pro user: full profile (field, rate, address, lat, lon, published)
|
||
router.post("/", requireAuth, async (req, res) => {
|
||
try {
|
||
const user = await User.findById(req.user.id);
|
||
|
||
if (req.user.role === "pro") {
|
||
const {
|
||
firstName,
|
||
lastName,
|
||
phone,
|
||
field,
|
||
rate,
|
||
address,
|
||
description,
|
||
picture,
|
||
published,
|
||
} = req.body;
|
||
|
||
// pure publish toggle?
|
||
if (
|
||
req.body.hasOwnProperty("published") &&
|
||
Object.keys(req.body).length === 1
|
||
) {
|
||
// disallow publishing unless all fields are set
|
||
if (published) {
|
||
const pf = (await User.findById(req.user.id)).profile;
|
||
if (
|
||
!pf.firstName ||
|
||
!pf.lastName ||
|
||
!pf.phone ||
|
||
!pf.field ||
|
||
pf.rate == null ||
|
||
!pf.address ||
|
||
!pf.description || // ← require description
|
||
!pf.picture // ← require picture
|
||
) {
|
||
return res.status(400).json({
|
||
error:
|
||
"Complete firstName, lastName, phone, field, rate, address, description & picture before publishing.",
|
||
});
|
||
}
|
||
}
|
||
user.profile.published = !!published;
|
||
await user.save();
|
||
return res.json({ success: true });
|
||
}
|
||
|
||
// full update → geocode + persist all fields
|
||
const geoRes = await fetch(
|
||
`https://maps.googleapis.com/maps/api/geocode/json` +
|
||
`?address=${encodeURIComponent(address)}` +
|
||
`&key=${process.env.GOOGLE_PLACES_API_KEY}`
|
||
);
|
||
const geoData = await geoRes.json();
|
||
console.log(
|
||
"🗺️ geoData.results:",
|
||
JSON.stringify(geoData.results, null, 2)
|
||
);
|
||
if (geoData.status !== "OK" || !geoData.results.length) {
|
||
return res.status(400).json({ error: "Invalid address" });
|
||
}
|
||
|
||
// prefer a true city‐level result, then region, then country, then anything
|
||
const place =
|
||
geoData.results.find((r) => r.types.includes("locality")) ||
|
||
geoData.results.find((r) =>
|
||
r.types.includes("administrative_area_level_2")
|
||
) ||
|
||
geoData.results.find((r) =>
|
||
r.types.includes("administrative_area_level_1")
|
||
) ||
|
||
geoData.results[0];
|
||
|
||
const { lat, lng } = place.geometry.location;
|
||
|
||
// first try the locality component
|
||
const cityComp = place.address_components.find((c) =>
|
||
c.types.includes("locality")
|
||
);
|
||
let rawCity;
|
||
if (cityComp) {
|
||
rawCity = cityComp.long_name;
|
||
} else {
|
||
// use the penultimate segment of formatted_address, e.g.
|
||
// ["Hlavná","Old Town","Košice","Slovakia"] → "Košice"
|
||
const parts = (place.formatted_address || "")
|
||
.split(",")
|
||
.map((s) => s.trim());
|
||
rawCity = parts.length > 1 ? parts[parts.length - 2] : parts[0];
|
||
}
|
||
|
||
// normalize: strip accents + lowercase
|
||
const city = rawCity
|
||
.normalize("NFD")
|
||
.replace(/[\u0300-\u036f]/g, "")
|
||
.toLowerCase();
|
||
|
||
// now persist everything
|
||
user.profile.firstName = firstName;
|
||
user.profile.lastName = lastName;
|
||
user.profile.phone = phone;
|
||
user.profile.field = field;
|
||
user.profile.rate = rate;
|
||
user.profile.address = address;
|
||
user.profile.description = description; // ← save description
|
||
user.profile.picture = picture; // ← save picture
|
||
user.profile.city = city;
|
||
user.profile.location = {
|
||
type: "Point",
|
||
coordinates: [lng, lat],
|
||
};
|
||
user.profile.published = !!published;
|
||
} else {
|
||
// ─── Normal user profile update ──────────────────────────────────────
|
||
// Allow them to save firstName, lastName, phone, and (optionally) password
|
||
const { firstName, lastName, phone, password } = req.body;
|
||
const u = await User.findById(req.user.id);
|
||
if (!u) return res.status(404).json({ error: "User not found" });
|
||
|
||
if (firstName !== undefined) u.profile.firstName = firstName;
|
||
if (lastName !== undefined) u.profile.lastName = lastName;
|
||
if (phone !== undefined) u.profile.phone = phone;
|
||
// if they submitted a new password, hash + save it
|
||
if (password) {
|
||
u.password = await bcrypt.hash(password, 10);
|
||
}
|
||
|
||
await u.save();
|
||
return res.json({ success: true, profile: u.profile });
|
||
}
|
||
|
||
await user.save();
|
||
res.json({ success: true });
|
||
} catch (err) {
|
||
res.status(500).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
// DELETE /api/profiles/me → unpublish pro or delete normal account
|
||
router.delete("/me", requireAuth, async (req, res) => {
|
||
try {
|
||
// Delete the entire user account
|
||
await User.findByIdAndDelete(req.user.id);
|
||
return res.json({ success: true });
|
||
} catch (err) {
|
||
res.status(500).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
export default router;
|