поиск по городам из json файла не работает, подсказки по городу не работают, город при регистрации в register.ejs регистрируется пустой region вместо city, в коде много ошибок. в musicians.json сохраняется “region”:“”, а не city. location и все что с этим связано мы теперь не используем, вместо этого используем city.
app.js:
const express = require(“express”);
const fs = require(“fs”);
const session = require(“express-session”);
const fileUpload = require(“express-fileupload”);
const app = express();
const fuzzball = require(“fuzzball”);

app.set(“view engine”, “ejs”);
app.use(express.static(“public”));
app.use(express.urlencoded({ extended: true }));
app.use(fileUpload());
app.use(session({
secret: “mysecretkey”,
resave: false,
saveUninitialized: false
}));




/* function normalizeLocationName(userInput) {
//const matches = fuzzball.extract(userInput, locationNames, { scorer: fuzzball.token_set_ratio, returnObjects: true });
const bestMatch = matches.sort((a, b) => b.score - a.score)[0];

return bestMatch.score >= 75 ? bestMatch.choice : userInput;
} */

const citiesAndRegions = JSON.parse(fs.readFileSync(“./db/russia.json”));

const predefinedGenres = [‘Rock’, ‘Pop’, ‘Jazz’, ‘Hip Hop’, ‘Electronic’, ‘Blues’];

function getMusicianById(id) {
const data = fs.readFileSync(“./db/musicians.json”);
const musicians = JSON.parse(data);

return musicians.musicians.find(musician => musician.id === id);
}

function requireLogin(req, res, next) {
if (req.session.musicianId) {
next();
} else {
res.redirect(“/login”);
}
}

function search(query = ‘’, role = ‘’, city = ‘’) {
const data = fs.readFileSync(‘./db/musicians.json’);
const musicians = JSON.parse(data).musicians.map(musician => {
return {
name: musician.name,
genre: musician.genre,
originalName: musician.name,
profileLink: /profile/${musician.id},
thumbnail: musician.thumbnail,
soundcloud: musician.soundcloud,
role: musician.role,
location: musician.location
};
});

let results = [];

if (query || role || city) {
const lowerQuery = query.toLowerCase();
results = musicians.filter(musician => {
const nameScore = musician.name.toLowerCase().startsWith(lowerQuery) ? 2 : musician.name.toLowerCase().includes(lowerQuery) ? 1 : 0;
const genreScore = musician.genre.toLowerCase().startsWith(lowerQuery) ? 2 : musician.genre.toLowerCase().includes(lowerQuery) ? 1 : 0;
const locationScore = fuzzball.token_set_ratio(musician.location.toLowerCase(), location.toLowerCase()); // изменено здесь
const cityMatch = musician.city && musician.city.toLowerCase() === city.toLowerCase();
return (
nameScore + genreScore + locationScore > 0 &&
(role === “” || musician.role === role) &&
(location === “” || locationScore >= 75) &&
(city === “” || cityMatch)
);
}).sort((a, b) => {
const aNameScore = a.name.toLowerCase().startsWith(lowerQuery) ? 2 : a.name.toLowerCase().includes(lowerQuery) ? 1 : 0;
const bNameScore = b.name.toLowerCase().startsWith(lowerQuery) ? 2 : b.name.toLowerCase().includes(lowerQuery) ? 1 : 0;
const aGenreScore = a.genre.toLowerCase().startsWith(lowerQuery) ? 2 : a.genre.toLowerCase().includes(lowerQuery) ? 1 : 0;
const bGenreScore = b.genre.toLowerCase().startsWith(lowerQuery) ? 2 : b.genre.toLowerCase().includes(lowerQuery) ? 1 : 0;

// Sort by name score, then genre score, then location score (descending)
if (aNameScore + aGenreScore + a.location < bNameScore + bGenreScore + b.location) {
return 1;
} else if (aNameScore + aGenreScore + a.location > bNameScore + bGenreScore + b.location) {
return -1;
} else {
return 0;
}
});

// Remove duplicates
results = results.filter((result, index, self) =>
index === self.findIndex(r => (
r.name === result.name && r.genre === result.genre && r.location === result.location
))
);
}

return results;
}

app.use((req, res, next) => {
if (req.session.musicianId) {
const musician = getMusicianById(req.session.musicianId);
res.locals.musician = musician;
res.locals.userLoggedIn = true;
res.locals.username = musician.name;
} else {
res.locals.userLoggedIn = false;
}

next();
});

app.get(“/”, (req, res) => {
const data = fs.readFileSync(“./db/musicians.json”);
const musicians = JSON.parse(data);

res.render(“index”, { musicians: musicians.musicians });
});

app.get(“/register”, (req, res) => {
if (req.session.musicianId) {
const musician = getMusicianById(req.session.musicianId);
res.redirect(“/profile/” + musician.id);
} else {



res.render(“register”, { citiesAndRegions, city:‘’ });
}
});

app.post(“/register”, (req, res) => {
if (req.session.musicianId) {
const musician = getMusicianById(req.session.musicianId);
res.redirect(“/profile/” + musician.id);
} else {
const data = fs.readFileSync(“./db/musicians.json”);
const musicians = JSON.parse(data);

const newMusician = {
id: musicians.musicians.length + 1,
name: req.body.name,
genre: req.body.genre,
instrument: req.body.instrument,
soundcloud: req.body.soundcloud,
password: req.body.password,
role: req.body.role,
city: req.body.city,
login: req.body.login
};




if (req.files && req.files.thumbnail) {
const file = req.files.thumbnail;
const filename = “musician_” + newMusician.id + “" + file.name;
file.mv(“./public/img/” + filename);

newMusician.thumbnail = filename;
}

const found = citiesAndRegions.find(
({ city }) => city === req.body.city.toLowerCase()
);

// Если найдено - сохраняем город и регион, если нет - оставляем только город
if (found) {
newMusician.city = found.city;
newMusician.region = found.region;
} else {
newMusician.city = req.body.city;
newMusician.region = “”;
}

musicians.musicians.push(newMusician);
fs.writeFileSync(“./db/musicians.json”, JSON.stringify(musicians));

req.session.musicianId = newMusician.id;
res.redirect(“/profile/” + newMusician.id);
}
});

app.get(“/profile/:id”, (req, res) => {
const musician = getMusicianById(parseInt(req.params.id));

if (musician) {
res.render(“profile”, { musician: musician });
} else {
res.status(404).send(“Musician not found”);
}
});

app.get(“/login”, (req, res) => {
res.render(“login”);
});

app.post(“/login”, (req, res) => {
const data = fs.readFileSync(“./db/musicians.json”);
const musicians = JSON.parse(data);

const musician = musicians.musicians.find(musician => musician.login === req.body.login && musician.password === req.body.password);

if (musician) {
req.session.musicianId = musician.id;
res.redirect(“/profile/” + musician.id);

} else {
res.render(“login”, { error: “Invalid login or password” });
}
});

app.get(“/logout”, (req, res) => {
req.session.destroy();
res.redirect(“/”);
});

app.get(‘/search’, (req, res) => {
const query = req.query.query || ‘’;
const role = req.query.role || ‘’;
const city = req.query.city || ‘’;
let musicians = [];

if (query || role || city) {
musicians = search(query, role, city);
} else {
const data = fs.readFileSync(‘./db/musicians.json’);
musicians = JSON.parse(data).musicians.map(musician => {
return {
name: musician.name,
genre: musician.genre,
originalName: musician.name,
profileLink: /profile/${musician.id},
thumbnail: musician.thumbnail,
soundcloud: musician.soundcloud,
role: musician.role,
location: musician.location
};
});
}

res.locals.predefinedGenres = predefinedGenres;
res.render(‘search’, { musicians, query, role, citiesAndRegions });

//res.redirect(‘/search’);
});

app.get(“/profile/:id/edit”, requireLogin, (req, res) => {
const musician = getMusicianById(parseInt(req.params.id));

if (musician) {
if (req.session.musicianId === musician.id) { // Check if the logged-in user is the owner of the profile
res.render(“edit-profile”, { musician: musician });
} else {
res.status(403).send(“Access denied”);
}
} else {
res.status(404).send(“Musician not found”);
}
});





app.post(‘/profile/:id/edit’, requireLogin, (req, res) => {
const musician = getMusicianById(parseInt(req.params.id));

if (musician) {
if (!req.body.name || !req.body.genre) {
res.status(400).send(‘Please fill out all fields’);

} else {
musician.name = req.body.name;
musician.genre = req.body.genre;
musician.instrument = req.body.instrument;
musician.soundcloud = req.body.soundcloud;
musician.soundcloud1 = req.body.soundcloud1;
musician.soundcloud2 = req.body.soundcloud2;
musician.location = req.body.location;
musician.role = req.body.role;
musician.bio = req.body.bio;

if (req.files && req.files.thumbnail) {
const file = req.files.thumbnail;
const filename = 'musician’ + musician.id + ‘_’ + file.name;
file.mv(‘./public/img/’ + filename);
musician.thumbnail = filename;
}

const data = fs.readFileSync(‘./db/musicians.json’);
const musicians = JSON.parse(data);
const index = musicians.musicians.findIndex(m => m.id === musician.id);
musicians.musicians[index] = musician;

fs.writeFileSync(‘./db/musicians.json’, JSON.stringify(musicians));

res.redirect(‘/profile/’ + musician.id);
}
} else {
res.status(404).send(‘Musician not found’);
}


});

function isValidSoundCloudUrl(url) {
return url.startsWith(‘https://soundcloud.com/’);
}

app.listen(3000, () => {
console.log(“Server started on port 3000”);
});

register.ejs:
<!DOCTYPE html>
<html lang=“en”>
<head>
<meta charset=“UTF-8” />
<meta name=“viewport” content=“width=device-width, initial-scale=1.0” />
<meta http-equiv=“X-UA-Compatible” content=“ie=edge” />
<link rel=“stylesheet” href=”/css/main.css" />
<link rel=“stylesheet” href=“/node_modules/jquery-ui/themes/base/all.css” />
<script src=“/node_modules/jquery/dist/jquery.min.js”></script>
<script src=“/node_modules/jquery-ui-dist/jquery-ui.min.js”></script>
<title>Register as a Musician</title>
</head>
<body>
<header>
<nav>
<ul>
<li><a href=“/”>Home</a></li>
<li><a href=“/register”>Register</a></li>
<li><a href=“/search”>Search</a></li>
</ul>
</nav>
</header>
<main>
<h1>Register as a Musician</h1>
<form method=“post” enctype=“multipart/form-data”>
<label for=“name”>Name</label>
<input type=“text” id=“name” name=“name” required>

<label for=“role”>Role</label>
<select id=“role” name=“role” required onchange=“showInstrument(this.value)”>
<option value=“”>Select a role</option>
<option value=“Band”>A band</option>
<option value=“Artist”>Artist</option>
</select>

<label for=“genre”>Genre</label>
<select id=“genre” name=“genre” required>
<option value=“”>Select a genre</option>
<option value=“Rock”>Rock</option>
<option value=“Pop”>Pop</option>
<option value=“Hip hop”>Hip hop</option>
<option value=“Electronic”>Electronic</option>
</select>

<label for=“instrument” id=“instrument-label” style=“display: none”>Instrument</label>
<select id=“instrument” name=“instrument” style=“display: none”>
<option value=“”>Select a instrument</option>
<option value=“Bass”>Bass</option>
<option value=“Rythm guitar”>Rythm guitar</option>
<option value=“Lead guitar”>Lead guitar</option>
<option value=“Vocal”>Vocal</option>
</select>

<label for=“soundcloud”>SoundCloud URL</label>
<input type=“url” id=“soundcloud” name=“soundcloud”>

<label for=“password”>Password</label>
<input type=“password” id=“password” name=“password” required>



<label for=“city”>City:</label>
<input id=“city” name=“city” type=“text” value=“<%= citiesAndRegions[0].city %>”><br />

<label for=“login”>Login</label>
<input type=“text” id=“login” name=“login” required>

<label for=“thumbnail”>Thumbnail</label>
<input type=“file” id=“thumbnail” name=“thumbnail”>

<button type=“submit”>Register</button>
</form>
</main>

<script>
function showInstrument(role) {
const instrumentLabel = document.querySelector(‘#instrument-label’);
const instrumentSelect = document.querySelector(‘#instrument’);
if (role === ‘Artist’) {
instrumentLabel.style.display = ‘block’;
instrumentSelect.style.display = ‘block’;
} else {
instrumentLabel.style.display = ‘none’;
instrumentSelect.style.display = ‘none’;
}
}
</script>

<script>
(function () {
const cities = <%= JSON.stringify(citiesAndRegions.map(item => item.city)) %>;


(“#city”).autocomplete({
source: cities,
minLength: 3,
});
});
</script>

</body>
</html>
search.ejs:
<!DOCTYPE html>
<html>
<head>
<title>Search Musicians</title>
<link rel=“stylesheet” href=“/node_modules/jquery-ui/themes/base/all.css” />
<script src=“/node_modules/jquery/dist/jquery.min.js”></script>
<script src=“/node_modules/jquery-ui-dist/jquery-ui.min.js”></script>
</head>
<body>
<h1>Search Musicians</h1>
<form action=“/search” method=“get”>
<label for=“query”>Search by name or genre:</label> <input id=“query” name=“query” type=“text” value=“<%= query %>”><br>
<br>
<label for=“role”>Search by role:</label> <select id=“role” name=“role”>
<option value=“”>
All
</option>
<option value=“Band”>
Band
</option>
<option value=“Artist”>
Artist
</option>
</select>
<label for=“city”>Search by location:</label>
<input id=“city” name=“city” type=“text” value=“<%= musician.city %>”>
<br>
<!-- Add new input field for location -->


<br>

<br>
<button type=“submit”>Search</button>
</form><%if (musicians.length > 0) { %>
<h2>Results:</h2>
<ul>
<%musicians.forEach(musician => { %>
<li>
<a href=“<%= musician.profileLink %>”><%= musician.name %> <%if (musician.thumbnail) { %> <img src=“/img/<%= musician.thumbnail %>” alt=“<%= musician.name %>”> <%} %></a> - <%= musician.genre %> - <%= musician.location %> <%if (musician.soundcloud) { %> <a href=“%3C%=%20musician.soundcloud%20%%3E”>SoundCloud</a> <%} %>
</li><%}); %>
</ul><%} else if (query || role) { %>
<p>No musicians found.</p><%} %>
<script>
(function () {
const cities = <%= JSON.stringify(citiesAndRegions.map(item => item.city)) %>;


(“#location”).autocomplete({
source: cities,
minLength: 3,
});
});
</script>
<script>
const city = document.querySelector(“#city”).value;
const url =
“/search?query=” +
encodeURIComponent(query) +
“&role=” +
encodeURIComponent(role) +
“&location=” +
encodeURIComponent(location) +
“&city=” +
encodeURIComponent(city);
</script>
</body>
</html>
musicians.json:
{“musicians”:[{“id”:1,“name”:“sukaAAAAAAA”,“genre”:“BluesZ”,“instrument”:“Guitar”,“soundcloud”:“http://soundcloud.com/dasdasdasd",“password”:“password123”,“location”:"New York”,“login”:“suka”,“bio”:“”,“region”:“”},

russia.json (где хранятся города) выглядит так:
[
{
“region”: “Москва и Московская обл.”,
“city”: “Москва”
},
{
“region”: “Москва и Московская обл.”,
“city”: “Абрамцево”
},