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 inFileStream = fs.createReadStream("./output.csv");
await db.query("BEGIN");
const rl = readline.createInterface({
input: inFileStream,
output: null,
output: null,
crlfDelay: Infinity
})
rl.on('line', async (line) => {
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', () => {
console.log("done");
rl.on('close', async () => {
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 TABLE IF NOT EXISTS cities (
name VARCHAR(32),
name VARCHAR(64),
lat 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) {
await client.query("DELETE FROM cities");
//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) => {
if (error) logError(error);
@ -61,33 +61,50 @@ const eraseUserMedia = async (roomId) => {
};
const selectProfilesForUser = async (roomId) => {
let myInterest = (await db.query("SELECT interest FROM users WHERE room_id = $1", [roomId])).rows[0].interest;
let mySex = (await db.query("SELECT sex FROM users WHERE room_id = $1", [roomId])).rows[0].sex;
let userAge = (await db.query("SELECT age FROM users WHERE room_id = $1", [roomId])).rows[0].age;
//Selecting profiles other than user's and with difference in age +-2.
let user;
if (myInterest === 'b') // both, no matter what sex
user = (await db.query(`SELECT
room_id, name, age, sex, description, country, city FROM users
WHERE
age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
AND room_id != $2
AND (interest = $3 OR interest = 'b')
ORDER BY RANDOM()
LIMIT 1`, [userAge, roomId, mySex])
).rows[0];
else {
user = (await db.query(`SELECT
room_id, name, age, sex, description, country, city FROM users
WHERE
age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
AND room_id != $2
AND sex = $3
AND (interest = $4 OR interest = 'b')
ORDER BY RANDOM()
LIMIT 1`, [userAge, roomId, myInterest, mySex])
).rows[0];
}
const { myrange, myinterest, mysex, myage, mycity, mycountry } = (await db.query(`SELECT range AS myrange,
interest AS myinterest,
sex AS mysex,
age AS myage,
city AS mycity,
country AS mycountry
FROM users WHERE room_id = $1`, [roomId])
).rows[0];
const { lat, lng } = (await db.query("SELECT lat, lng FROM cities WHERE name = $1 AND country = $2", [mycity, mycountry])).rows[0];
//Selecting profiles other than user's and fitting their needs
/*
2 * ASIN(SQRT(
POWER(SIN( (deg2rad(lat - $6::double precision)) / 2 ), 2)
+ COS(deg2rad(lat))
* COS(deg2rad($6::double precision))
* POWER(SIN(deg2rad(lng - $7::double precision)/2), 2)
))
* 6371 <= $5::double precision
*/
let user = (await db.query(`SELECT
room_id, name, age, sex, description, country, city FROM users
WHERE
age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
AND room_id != $2
AND ${myinterest !== 'b' ? "sex = $3" : "$3 = $3 AND $4 = $4 AND $5 = $5 AND $6 = $6 AND $7 = $7"}
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;
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;
}
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 {
eraseUser,
appendUserPictures,
@ -196,5 +250,7 @@ export {
getUnreadMessages,
markMessageAsRead,
setUserLanguage,
getUserLanguage
getUserLanguage,
checkCountry,
checkCity
};

View File

@ -32,7 +32,9 @@ import {
setUserState,
uploadMediaAsMessage,
setUserLanguage,
getUserLanguage
getUserLanguage,
checkCountry,
checkCity
} from './db.js';
import { processRequest, showRandomProfileToUser, showNewLikes } from "./interactions.js";
@ -84,15 +86,41 @@ client.on("room.message", async (roomId, event) => {
break;
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');
break;
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');
break;
case "range":
answer = parseInt(answer.split(" ")[0]);
console.log(answer)
if (!answer) {
if (!answer && answer != 0) {
await client.sendText(roomId, i18n.t(["setup", "range"]));
return;
}
@ -187,7 +215,7 @@ client.on("room.message", async (roomId, event) => {
return;
} else if (answer == '🏠️' || answer == '4') {
await client.sendText(roomId, messages.general.menu);
await client.sendText(roomId, i18n.t(["general", "menu"]));
await setUserState(roomId, 'menu');
return;
}

View File

@ -87,8 +87,7 @@ ${chosenProfile.description}`;
}
}
await client.sendText(roomId, messages.general.rate);
await client.sendText(roomId, i18n.t(["general", "rate"]));
};
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!",
"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!",
"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": "Я не понимаю ваш ответ. Пожалуйста, перечитайте моё прошлое сообщение и попробуйте снова!",
"notimplemented": "Эта возможность ещё не добавлена! Пожалуйста, следите за обновлениями на моём git'е: https://git.foxarmy.org/leca/heart2heart. Доставайте своих админов, чтобы обновлялись, когда новая версия выходит ;)",
"noprofiles": "Не могу найти профили, которые подходят вашим критериям! Извините. Попробуйте позже!",
"alreadymessaged": "Вы уже отправляли сообщение этому человеку. Ваше сообщение не было отправлено!"
"alreadymessaged": "Вы уже отправляли сообщение этому человеку. Ваше сообщение не было отправлено!",
"wrongcountry": "Не знаю такой страны. Возможно, вы имели в виду %{first}, %{second} или %{third}?",
"wrongcity": "Не знаю такого города. Возможно, вы имели в виду %{first}, %{second}, %{third}, %{fourth} или %{fifth}?"
}
}