Complete location as a criteria for search

This commit is contained in:
leca 2024-08-10 15:47:37 +03:00
parent 7220c6d2c2
commit 971d263d35
8 changed files with 42614 additions and 702 deletions

43119
cities.sql

File diff suppressed because it is too large Load Diff

View File

@ -25,18 +25,21 @@ const db = await getClient();
const file = fs.readFileSync("./output.csv").toString(); const file = fs.readFileSync("./output.csv").toString();
const inFileStream = fs.createReadStream("./output.csv"); const inFileStream = fs.createReadStream("./output.csv");
await db.query("BEGIN");
const rl = readline.createInterface({ const rl = readline.createInterface({
input: inFileStream, input: inFileStream,
output: null, output: null,
crlfDelay: Infinity crlfDelay: Infinity
}) })
rl.on('line', async (line) => { rl.on('line', async (line) => {
let splitted = line.split(",") let splitted = line.split(",")
await db.query(`INSERT INTO cities (name, lat, lng, country) VALUES ($1, $2, $3, $4);`, [splitted[0], splitted[1], splitted[2], splitted[3]]) console.log(splitted);
await db.query(`INSERT INTO cities (name, lat, lng, country) VALUES ($1, $2, $3, $4)`, [splitted[0], splitted[1], splitted[2], splitted[3]])
}); });
rl.on('close', () => { rl.on('close', async () => {
console.log("done"); await db.query("COMMIT");
process.exit(0)
}) })

View File

@ -40,8 +40,24 @@ CREATE TABLE IF NOT EXISTS messages (
CREATE UNIQUE INDEX IF NOT EXISTS unique_messages ON messages(sender, recipient); CREATE UNIQUE INDEX IF NOT EXISTS unique_messages ON messages(sender, recipient);
CREATE TABLE IF NOT EXISTS cities ( CREATE TABLE IF NOT EXISTS cities (
name VARCHAR(32), name VARCHAR(64),
lat REAL, lat REAL,
lng REAL, lng REAL,
country VARCHAR(32) country VARCHAR(64)
); );
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch SCHEMA public;
CREATE OR REPLACE FUNCTION deg2rad(double precision) RETURNS double precision AS 'SELECT $1 * 0.01745329' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT;
-- lat1, lng1, lat2, lng2, range
CREATE OR REPLACE FUNCTION check_distance(double precision, double precision, double precision, double precision, double precision) RETURNS BOOLEAN AS '
SELECT 2 * ASIN(SQRT(
POWER(SIN( (deg2rad($3 - $1)) / 2 ), 2)
+ COS(deg2rad($1))
* COS(deg2rad($3))
* POWER(SIN(deg2rad($4 - $2)/2), 2)
))
* 6371 <= $5
' LANGUAGE SQL RETURNS NULL ON NULL INPUT;

114
src/db.js
View File

@ -19,7 +19,7 @@ const getClient = async () => {
if ((await client.query("SELECT * FROM cities")).rowCount < 6079) { if ((await client.query("SELECT * FROM cities")).rowCount < 6079) {
await client.query("DELETE FROM cities"); await client.query("DELETE FROM cities");
//Not sure if pg has support for such kind of things, sooooooooo //Not sure if pg has support for such kind of things, sooooooooo
exec(`psql -h ${process.env.POSTGRES_HOST} -p ${process.env.POSTGRES_PORT} -d ${process.env.POSTGRES_DB} -U ${process.env.POSTGRES_USER} -f ./cities.sql`, (error) => { exec(`psql -h ${process.env.POSTGRES_HOST} -p ${process.env.POSTGRES_PORT} -d ${process.env.POSTGRES_DB} -U ${process.env.POSTGRES_USER} -f ./cities.sql`, (error) => {
if (error) logError(error); if (error) logError(error);
@ -61,33 +61,50 @@ const eraseUserMedia = async (roomId) => {
}; };
const selectProfilesForUser = async (roomId) => { const selectProfilesForUser = async (roomId) => {
let myInterest = (await db.query("SELECT interest FROM users WHERE room_id = $1", [roomId])).rows[0].interest; const { myrange, myinterest, mysex, myage, mycity, mycountry } = (await db.query(`SELECT range AS myrange,
let mySex = (await db.query("SELECT sex FROM users WHERE room_id = $1", [roomId])).rows[0].sex; interest AS myinterest,
let userAge = (await db.query("SELECT age FROM users WHERE room_id = $1", [roomId])).rows[0].age; sex AS mysex,
//Selecting profiles other than user's and with difference in age +-2. age AS myage,
let user; city AS mycity,
if (myInterest === 'b') // both, no matter what sex country AS mycountry
user = (await db.query(`SELECT FROM users WHERE room_id = $1`, [roomId])
room_id, name, age, sex, description, country, city FROM users ).rows[0];
WHERE const { lat, lng } = (await db.query("SELECT lat, lng FROM cities WHERE name = $1 AND country = $2", [mycity, mycountry])).rows[0];
age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)]) //Selecting profiles other than user's and fitting their needs
AND room_id != $2 /*
AND (interest = $3 OR interest = 'b') 2 * ASIN(SQRT(
ORDER BY RANDOM() POWER(SIN( (deg2rad(lat - $6::double precision)) / 2 ), 2)
LIMIT 1`, [userAge, roomId, mySex]) + COS(deg2rad(lat))
).rows[0]; * COS(deg2rad($6::double precision))
else { * POWER(SIN(deg2rad(lng - $7::double precision)/2), 2)
user = (await db.query(`SELECT ))
room_id, name, age, sex, description, country, city FROM users * 6371 <= $5::double precision
WHERE */
age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)]) let user = (await db.query(`SELECT
AND room_id != $2 room_id, name, age, sex, description, country, city FROM users
AND sex = $3 WHERE
AND (interest = $4 OR interest = 'b') age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
ORDER BY RANDOM() AND room_id != $2
LIMIT 1`, [userAge, roomId, myInterest, mySex]) AND ${myinterest !== 'b' ? "sex = $3" : "$3 = $3 AND $4 = $4 AND $5 = $5 AND $6 = $6 AND $7 = $7"}
).rows[0]; AND ${myrange !== 0 ?
} `city = ANY(ARRAY(SELECT name
FROM cities
WHERE name = ANY(ARRAY(SELECT name
FROM cities
WHERE
check_distance($6::double precision, $7::double precision, lat, lng, $5::double precision)
))
))`
:
`check_distance($6::double precision, $7::double precision, (SELECT lat FROM cities WHERE name = city AND cities.country = country), (SELECT lng FROM cities WHERE name = city AND cities.country = country), range)`
}
AND (range >= $5::double precision AND range != 0)
AND (interest = $4 OR interest = 'b')
ORDER BY RANDOM()
LIMIT 1`, [myage, roomId, myinterest, mysex, myrange, lat, lng])
).rows[0];
if (!user) return null; if (!user) return null;
let media = await getUserProfilePictures(user.room_id); let media = await getUserProfilePictures(user.room_id);
@ -174,6 +191,43 @@ const getUserLanguage = async (roomId) => {
return (await db.query("SELECT language FROM users WHERE room_id = $1", [roomId])).rows[0].language; return (await db.query("SELECT language FROM users WHERE room_id = $1", [roomId])).rows[0].language;
} }
const checkCountry = async (name) => {
let res = (await db.query(`SELECT country AS name, levenshtein(country, $1) AS similarity
FROM cities
ORDER BY similarity ASC
LIMIT 3`, [name])
).rows;
if (res[0].similarity == 0) { // 'similarity' is actually inversed. The less 'similarity', the more it similar. 0 means the same
return {
exists: true
};
}
return {
exists: false,
variants: res
};
}
const checkCity = async (name) => {
let res = (await db.query(`SELECT name, country, levenshtein(name, $1) AS similarity
FROM cities
ORDER BY similarity ASC
LIMIT 5`, [name])
).rows;
if (res[0].similarity == 0) {
return {
exists: true,
country: res[0].country
}
}
return {
exists: false,
variants: res
}
}
export { export {
eraseUser, eraseUser,
appendUserPictures, appendUserPictures,
@ -196,5 +250,7 @@ export {
getUnreadMessages, getUnreadMessages,
markMessageAsRead, markMessageAsRead,
setUserLanguage, setUserLanguage,
getUserLanguage getUserLanguage,
checkCountry,
checkCity
}; };

View File

@ -32,7 +32,9 @@ import {
setUserState, setUserState,
uploadMediaAsMessage, uploadMediaAsMessage,
setUserLanguage, setUserLanguage,
getUserLanguage getUserLanguage,
checkCountry,
checkCity
} from './db.js'; } from './db.js';
import { processRequest, showRandomProfileToUser, showNewLikes } from "./interactions.js"; import { processRequest, showRandomProfileToUser, showNewLikes } from "./interactions.js";
@ -84,15 +86,41 @@ client.on("room.message", async (roomId, event) => {
break; break;
case "country": case "country":
let checkResultCountry = await checkCountry(answer);
if (!checkResultCountry.exists) {
console.log(checkResultCountry.variants)
await client.sendText(roomId, i18n.t(
["errors", "wrongcountry"],
{
first: checkResultCountry.variants[0].name,
second: checkResultCountry.variants[1].name,
third: checkResultCountry.variants[2].name
}
));
return;
}
await processRequest(client, roomId, current_action, answer, 'city'); await processRequest(client, roomId, current_action, answer, 'city');
break; break;
case "city": case "city":
let checkResultCity = await checkCity(answer);
if (!checkResultCity.exists) {
await client.sendText(roomId, i18n.t(
["errors", "wrongcity"],
{
first: checkResultCity.variants[0].name,
second: checkResultCity.variants[1].name,
third: checkResultCity.variants[2].name,
fourth: checkResultCity.variants[3].name,
fifth: checkResultCity.variants[4].name,
}
));
return;
}
await processRequest(client, roomId, current_action, answer, 'range'); await processRequest(client, roomId, current_action, answer, 'range');
break; break;
case "range": case "range":
answer = parseInt(answer.split(" ")[0]); answer = parseInt(answer.split(" ")[0]);
console.log(answer) if (!answer && answer != 0) {
if (!answer) {
await client.sendText(roomId, i18n.t(["setup", "range"])); await client.sendText(roomId, i18n.t(["setup", "range"]));
return; return;
} }
@ -187,7 +215,7 @@ client.on("room.message", async (roomId, event) => {
return; return;
} else if (answer == '🏠️' || answer == '4') { } else if (answer == '🏠️' || answer == '4') {
await client.sendText(roomId, messages.general.menu); await client.sendText(roomId, i18n.t(["general", "menu"]));
await setUserState(roomId, 'menu'); await setUserState(roomId, 'menu');
return; return;
} }

View File

@ -87,8 +87,7 @@ ${chosenProfile.description}`;
} }
} }
await client.sendText(roomId, i18n.t(["general", "rate"]));
await client.sendText(roomId, messages.general.rate);
}; };
const showProfileToUser = async (client, roomId, profileId) => { const showProfileToUser = async (client, roomId, profileId) => {

View File

@ -37,6 +37,8 @@
"didntunderstand": "I cannot understand your answer. Please, read my question again and answer!", "didntunderstand": "I cannot understand your answer. Please, read my question again and answer!",
"notimplemented": "This feature is not yet implemented! Please, keep track of my updates at https://git.foxarmy.org/leca/heart2heart. Bother your admins to update, if released! ;)", "notimplemented": "This feature is not yet implemented! Please, keep track of my updates at https://git.foxarmy.org/leca/heart2heart. Bother your admins to update, if released! ;)",
"noprofiles": "There are no profiles left for you! I'm sorry. Please, come back later!", "noprofiles": "There are no profiles left for you! I'm sorry. Please, come back later!",
"alreadymessaged": "You have already messaged that person. Your message was not sent!" "alreadymessaged": "You have already messaged that person. Your message was not sent!",
"wrongcountry": "There is no such country. Perhaps, you meant %{first}, %{second} or %{third}?",
"wrongcity": "There is no such city. Perhaps, you meant %{first}, %{second}, %{third}, %{fourth} or %{fifth}?"
} }
} }

View File

@ -37,7 +37,8 @@
"didntunderstand": "Я не понимаю ваш ответ. Пожалуйста, перечитайте моё прошлое сообщение и попробуйте снова!", "didntunderstand": "Я не понимаю ваш ответ. Пожалуйста, перечитайте моё прошлое сообщение и попробуйте снова!",
"notimplemented": "Эта возможность ещё не добавлена! Пожалуйста, следите за обновлениями на моём git'е: https://git.foxarmy.org/leca/heart2heart. Доставайте своих админов, чтобы обновлялись, когда новая версия выходит ;)", "notimplemented": "Эта возможность ещё не добавлена! Пожалуйста, следите за обновлениями на моём git'е: https://git.foxarmy.org/leca/heart2heart. Доставайте своих админов, чтобы обновлялись, когда новая версия выходит ;)",
"noprofiles": "Не могу найти профили, которые подходят вашим критериям! Извините. Попробуйте позже!", "noprofiles": "Не могу найти профили, которые подходят вашим критериям! Извините. Попробуйте позже!",
"alreadymessaged": "Вы уже отправляли сообщение этому человеку. Ваше сообщение не было отправлено!" "alreadymessaged": "Вы уже отправляли сообщение этому человеку. Ваше сообщение не было отправлено!",
"wrongcountry": "Не знаю такой страны. Возможно, вы имели в виду %{first}, %{second} или %{third}?",
"wrongcity": "Не знаю такого города. Возможно, вы имели в виду %{first}, %{second}, %{third}, %{fourth} или %{fifth}?"
} }
} }