diff --git a/backend/models/Route.js b/backend/models/Route.js index 0d3ba0f..6a6ff17 100644 --- a/backend/models/Route.js +++ b/backend/models/Route.js @@ -11,11 +11,12 @@ const RouteSchema = new Schema( route: { type: Array, default: [] }, stickers: { type: Array, default: [] }, owner: { type: Schema.Types.ObjectId, ref: 'User' }, - logo: { type: String, default: 'DEFAULT' }, distance: { type: Number, default: 0 }, public: { type: Boolean, default: true }, created_at: { type: Date, default: Date.now() }, updated_at: { type: Date, default: Date.now() }, + logo: { type: String, default: 'DEFAULT' }, + map_style: { type: String, default: 'DEFAULT' }, }, ); diff --git a/backend/models/User.js b/backend/models/User.js index f0045c9..ceb8fbb 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -1,5 +1,4 @@ const mongoose = require('mongoose'); - const { Schema } = mongoose; // Schemas @@ -17,6 +16,7 @@ const UserSchema = new Schema( first_name: { type: String }, last_name: { type: String }, photo: { type: String }, + version: { type: Number, default: 2 }, routes: [{ type: Schema.Types.ObjectId, ref: 'Route' }] }, { diff --git a/backend/tools/import.js b/backend/tools/import.js new file mode 100755 index 0000000..89c2521 --- /dev/null +++ b/backend/tools/import.js @@ -0,0 +1,181 @@ +#!/usr/bin/env node +const mysql = require('mysql'); +const { REPLACEMENT, MAPS } = require('./stickersReplacement'); +const db = require('../config/db'); +let total_dist = 0; +const { User, Route } = require('../models'); + +const con = mysql.createConnection({ + host: 'vault48.org', + user: 'macos_exporter', + password: 'password', +}); + +const tryJSON = data => { + try { + return JSON.parse(data); + } catch (e) { + return {}; + } +}; + +function deg2rad(deg) { + return (deg * Math.PI) / 180; +} + +const findDistance = (t1, n1, t2, n2) => { + // convert coordinates to radians + const lat1 = deg2rad(t1); + const lon1 = deg2rad(n1); + const lat2 = deg2rad(t2); + const lon2 = deg2rad(n2); + + // find the differences between the coordinates + const dlat = lat2 - lat1; + const dlon = lon2 - lon1; + + // here's the heavy lifting + const a = (Math.sin(dlat / 2) ** 2) + + (Math.cos(lat1) * Math.cos(lat2) * (Math.sin(dlon / 2) ** 2)); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // great circle distance in radians + // const dm = c * 3961; // great circle distance in miles + const dk = c * 6373; // great circle distance in km + + // round the results down to the nearest 1/1000 + // const mi = round(dm); + return (Math.round(dk * 1000) / 1000); +}; + +const calcPolyDistance = route => { + let summ = 0; + for (let i = 1; i < route.length; i += 1) { + summ += findDistance(route[i - 1].lat, route[i - 1].lng, route[i].lat, route[i].lng); + } + + total_dist += summ; + + return parseFloat(Number(summ).toFixed(2)); +}; + +const stickerAngleParser = angle => parseFloat((Number(angle) - Math.PI).toFixed(2)); + +const stickerStyleParser = style => (REPLACEMENT[style] + ? { ...REPLACEMENT[style] } + : { set: 'base', sticker: 'chicken' }); + +const stickersParser = stickers => ( + stickers.map(({ + ang, latlng, style, text + }) => ({ + angle: stickerAngleParser(ang), // todo: change it! + latlng, + text, + ...stickerStyleParser(style), + })) +); + +const pointParser = points => ( + points.map(({ latlngs, text }) => ({ + text, + latlng: latlngs[0], + set: 'base', + sticker: 'empty', + angle: 2.2, + })) +); + +const mapStyleParser = style => (MAPS[style] + ? MAPS[style] + : 'DEFAULT' +); + +const run = async () => { + await con.connect(err => { if (err) throw err; }); + await con.query('use neu_map'); + + const users = await new Promise(resolve => con.query('SELECT * from tokens where role = "vk"', (err, rows) => { + resolve(rows.map(({ + login, token, created, role, data + }) => { + const info = tryJSON(data); + const created_at = new Date(created * 1000); + + return { + _id: login, + token, + created_at, + updated_at: created_at, + name: info.name, + first_name: (info.name && info.name.split(' ')[0]) || '', + last_name: (info.name && info.name.split(' ')[1]) || '', + role, + photo: info.photo, + version: 1, + }; + })); + })); + + const routes = await new Promise(resolve => con.query('SELECT routes.*, tokens.login as login from routes LEFT JOIN tokens ON tokens.id = routes.id WHERE tokens.login IS NOT NULL AND tokens.role="vk"', (err, rows) => { + if (err) console.log('error', err); + + resolve(rows.map(({ + data, created, name, login + }) => { + const { + map_style, route, stickers, points, logo + } = tryJSON(data); + const created_at = new Date(created * 1000); + + return { + _id: name, + owner: { _id: login }, + created_at, + updated_at: created_at, + map_style: mapStyleParser(map_style), + route, + stickers: [ + ...stickersParser(stickers), + ...pointParser(points) + ], + logo, + title: '', + version: 1, + distance: calcPolyDistance(route), + }; + })); + })); + + console.log('ended, got users:', users.length); + console.log('ended, got routes:', routes.length); + + const deletedUsers = await User.deleteMany({ + // version: 1 + }); + const deletedRoutes = await Route.deleteMany({ + // version: 1 + }); + console.log("dropped users", deletedUsers); + console.log("dropped routes", deletedRoutes); + console.log('inserting users'); + const addedUsers = (await Promise.all(users.map(user => User.create(user)))) + .reduce((obj, user) => ({ ...obj, [user._id]: user }), {}); + + console.log('inserting routes'); + const addedRoutes = await (await Promise.all(routes.map(route => Route.create({ + ...route, + owner: addedUsers[route.owner._id], + })))); + + await Promise.all(addedRoutes.map(async (route) => { + await addedUsers[route.owner._id].routes.push(route); + })); + + await Promise.all(addedRoutes.map(route => new Promise(resolve => ( + addedUsers[route.owner._id].save(() => resolve()) + )))); + + console.log('ok'); +}; + +run(); + diff --git a/backend/tools/stickersReplacement.js b/backend/tools/stickersReplacement.js new file mode 100644 index 0000000..082574e --- /dev/null +++ b/backend/tools/stickersReplacement.js @@ -0,0 +1,44 @@ +module.exports.REPLACEMENT = { + 0: { set: 'base', sticker: 'men' }, + 1: { set: 'base', sticker: 'square' }, + 2: { set: 'base', sticker: 'bridge' }, + 3: { set: 'base', sticker: 'ikea' }, + 4: { set: 'base', sticker: 'bugr' }, + 5: { set: 'base', sticker: 'monum' }, + 6: { set: 'base', sticker: 'opera' }, + 7: { set: 'base', sticker: 'forest' }, + 8: { set: 'base', sticker: 'empty' }, + 9: { set: 'base', sticker: 'empty' }, + 10: { set: 'base', sticker: 'road' }, + 11: { set: 'base', sticker: 'chicken' }, + 12: { set: 'base', sticker: 'camp' }, + 13: { set: 'base', sticker: 'fastfood' }, + 14: { set: 'base', sticker: 'beer' }, + 15: { set: 'base', sticker: 'pancake' }, + 16: { set: 'base', sticker: 'rocks' }, + 17: { set: 'real', sticker: 'swamp' }, + 18: { set: 'real', sticker: 'skull' }, + 19: { set: 'base', sticker: 'crap' }, + 20: { set: 'pin', sticker: 'start' }, + 21: { set: 'pin', sticker: 'p1' }, + 22: { set: 'pin', sticker: 'p2' }, + 23: { set: 'pin', sticker: 'p3' }, + 24: { set: 'pin', sticker: 'p4' }, + 25: { set: 'pin', sticker: 'p5' }, + 26: { set: 'pin', sticker: 'p6' }, + 27: { set: 'pin', sticker: 'finish' }, + 29: { set: 'pin', sticker: 'danger' }, + 30: { set: 'pin', sticker: 'question' }, +}; + +module.exports.MAPS = { + 'watercolor': 'BLANK', + 'darq': 'BLANK', + 'default': 'DGIS', + 'osm': 'DEFAULT', + 'hot': 'HOT', + 'blank': 'BLANK', + 'sat': 'DEFAULT', + 'ymap': 'DEFAULT', + 'ysat': 'DEFAULT', +}; diff --git a/package-lock.json b/package-lock.json index 582b9c0..28b9b04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2764,6 +2764,11 @@ "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "dev": true }, + "bignumber.js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", + "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" + }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", @@ -7658,8 +7663,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { "version": "3.0.3", @@ -9308,6 +9312,17 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, + "mysql": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.16.0.tgz", + "integrity": "sha512-dPbN2LHonQp7D5ja5DJXNbCLe/HRdu+f3v61aguzNRQIrmZLOeRoymBYyeThrR6ug+FqzDL95Gc9maqZUJS+Gw==", + "requires": { + "bignumber.js": "4.1.0", + "readable-stream": "2.3.6", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + } + }, "nan": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", @@ -10725,8 +10740,7 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "2.0.0", @@ -11257,7 +11271,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12407,6 +12420,11 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, "sshpk": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", @@ -12541,7 +12559,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13259,8 +13276,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", diff --git a/package.json b/package.json index e86b11b..75a3377 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "lodash": "^4.17.10", "mongoose": "^5.3.14", "morgan": "~1.9.0", + "mysql": "^2.16.0", "pt-sans-cyrillic": "0.0.4", "pug": "2.0.0-beta11", "raleway-cyrillic": "^4.0.2", diff --git a/src/constants/logos.js b/src/constants/logos.js index 5aadb2a..2e3a66b 100644 --- a/src/constants/logos.js +++ b/src/constants/logos.js @@ -5,8 +5,8 @@ export const LOGOS = { jolly: ['Пин-Микс + JW', 'http://map.vault48.org/misc/jw.png', 'top-right'], pedals: ['Усталые Педальки', 'http://map.vault48.org/misc/pedals.png', 'bottom-right'], rider: ['Райдер', 'http://map.vault48.org/misc/rider.png', 'bottom-right'], - // rider_evening: ['Вечерние городские', '/misc/rider_evening.png', 'top-right'], - // fas: ['Алкоспорт', '/misc/fas.png', 'bottom-right'], + rider_evening: ['Вечерние городские', '/misc/rider_evening.png', 'top-right'], + fas: ['Алкоспорт', '/misc/fas.png', 'bottom-right'], }; export const DEFAULT_LOGO = 'nvs'; diff --git a/src/constants/stickers.js b/src/constants/stickers.js index 31e87a5..28dd5ae 100644 --- a/src/constants/stickers.js +++ b/src/constants/stickers.js @@ -17,13 +17,14 @@ export const STICKERS = { opera: { off: 11, title: 'Оперный', title_long: 'Оперный театр' }, forest: { off: 1, title: 'Лес', title_long: 'Берёзовая роща' }, road: { off: 2, title: 'Трасса', title_long: 'Дорога' }, - chicken1: { off: 3, title: 'Курочка', title_long: 'Курочка' }, + chicken: { off: 3, title: 'Курочка', title_long: 'Курочка' }, camp: { off: 6, title: 'Палатка', title_long: 'Палаточный лагерь' }, rocks: { off: 14, title: 'Камни', title_long: 'Кааааммммуушшшки' }, crap: { off: 15, title: 'Щет', title_long: 'Полный щет' }, pancake: { off: 13, title: 'Блинцы', title_long: 'Блинчики' }, fastfood: { off: 16, title: 'Фастфуд', title_long: 'Быстрая еда' }, beer: { off: 12, title: 'Пивко', title_long: 'Пивко' }, + empty: { off: 20, title: 'Пусто', title_long: 'Пусто' }, } }, real: {