├── Procfile ├── views ├── favicon.png ├── portait.jpg ├── sw.js ├── index.ejs ├── style.css └── index.js ├── variables.env ├── vercel.json ├── package.json ├── app.json ├── README.md └── app.js /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | -------------------------------------------------------------------------------- /views/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaim-chv/zmanim-node/HEAD/views/favicon.png -------------------------------------------------------------------------------- /views/portait.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaim-chv/zmanim-node/HEAD/views/portait.jpg -------------------------------------------------------------------------------- /variables.env: -------------------------------------------------------------------------------- 1 | PUBLIC_VAPID_KEY = 'piblic vapid key' 2 | PRIVATE_VAPID_KEY = 'private vapid key' 3 | DBURL = 'mongoDB url' -------------------------------------------------------------------------------- /views/sw.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('push', (e) => { 2 | const data = e.data.json() 3 | self.registration.showNotification(data.title, { 4 | body: data.body, 5 | icon: data.icon 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "app.js", 6 | "use": "@now/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "app.js" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shkia", 3 | "version": "1.0.0", 4 | "description": "my first project, push notifications for shkia time!!", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "author": "chv", 10 | "license": "MPL-2.0", 11 | "dependencies": { 12 | "body-parser": "^1.19.0", 13 | "cookie-parser": "^1.4.5", 14 | "cors": "^2.8.5", 15 | "dotenv": "^8.2.0", 16 | "ejs": "^3.1.5", 17 | "express": "^4.17.1", 18 | "kosher-zmanim": "^0.8.0", 19 | "moment": "^2.29.1", 20 | "mongodb": "^3.6.3", 21 | "node-fetch": "^2.6.1", 22 | "node-geocoder": "^3.27.0", 23 | "node-schedule": "^1.3.2", 24 | "web-push": "^3.4.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zmanim-node", 3 | "description": "my first nodeJS app!! made to get zmaney halacha to every city and to get push notification before sunset", 4 | "repository": "https://github.com/chaim-chv/zmanim-node", 5 | "logo": "https://github.com/chaim-chv/zmanim-node/blob/master/views/portait.jpg", 6 | "env": { 7 | "GOOGLE": { 8 | "description": "google geolocation API key" 9 | }, 10 | "PUBLIC_VAPID_KEY": { 11 | "description": "generate it with - https://www.npmjs.com/package/web-push#command-line" 12 | }, 13 | "PRIVATE_VAPID_KEY": { 14 | "description": "generate it with - https://www.npmjs.com/package/web-push#command-line" 15 | }, 16 | "DBURL": { 17 | "description": "mongoDB url" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | זמני הלכה 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |

14 |
15 |

מערכת זמני הלכה

16 |

אפשר לבדוק כאן זמני הלכה בכל אחת מהערים (בארץ ישראל), וכן לקבל התראות לפני שקיעה בצורה אוטומטית לפי הבחירה שלכם!

17 |
18 |

בדיקת זמני הלכה

19 |
20 | 21 |
22 | 23 |

24 |

25 |

26 |

27 |

28 |

29 |
30 |
31 |
32 |

הרשמה להתראות על שקיעה

33 |

אפשר להירשם כאן לקבל פוש התראה רבע שעה לפני שקיעה בעיר שבחרתם:

34 |
35 | 36 | 37 |
38 | 39 |

40 |
41 |
42 |
43 | avatar 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /views/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Arimo&display=swap'); 2 | 3 | * { 4 | font-family: "arimo"; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | } 10 | 11 | .container { 12 | margin: 5%; 13 | } 14 | 15 | .userdiv { 16 | text-align: center; 17 | font-size: 1.2em; 18 | margin-top: -1.5em; 19 | } 20 | 21 | button { 22 | outline: 2px #ddefff solid; 23 | padding: 0.5% 3.5%; 24 | margin: 14px; 25 | background: #ddefff; 26 | transition: 1s; 27 | cursor: pointer; 28 | box-shadow: none; 29 | border: none; 30 | border-radius: 4px; 31 | } 32 | 33 | button:hover { 34 | outline: 2px #0c290c solid; 35 | } 36 | 37 | input { 38 | transition: 0.5s; 39 | outline: none; 40 | border: none; 41 | border-bottom: 2px #bbbbbb solid; 42 | text-align: center; 43 | } 44 | 45 | input:focus { 46 | border-bottom: 2px #235523 solid; 47 | } 48 | 49 | .title, 50 | .titledesc { 51 | text-align: center; 52 | } 53 | 54 | .titledesc { 55 | margin-bottom: 100px; 56 | } 57 | 58 | #zmanim, 59 | #pushes { 60 | width: 50%; 61 | position: absolute; 62 | width: 46%; 63 | height: 322px; 64 | text-align: center; 65 | border-radius: 10px; 66 | background: #ffffff; 67 | box-shadow: 0 0px 24.2px rgba(0, 0, 0, 0.021), 0 0px 39.9px rgba(0, 0, 0, 0.03), 0 0px 49.1px rgba(0, 0, 0, 0.037), 0 0px 55.2px rgba(0, 0, 0, 0.043), 0 0px 63.4px rgba(0, 0, 0, 0.05), 0 0px 80.9px rgba(0, 0, 0, 0.059), 0 0px 118px rgba(0, 0, 0, 0.08); 68 | } 69 | 70 | #zmanim { 71 | right: 2%; 72 | } 73 | 74 | #pushes { 75 | left: 2%; 76 | } 77 | 78 | fieldset { 79 | border: none; 80 | } 81 | 82 | .togglepush { 83 | background-color: #f7c78f; 84 | border-radius: 7px; 85 | padding: 0 7px 0 7px; 86 | } 87 | 88 | .togglepush:hover { 89 | background-color: #fadcba; 90 | } 91 | 92 | img { 93 | border-radius: 50%; 94 | width: 50px; 95 | height: 50px; 96 | position: fixed; 97 | bottom: 1.5em; 98 | left: 1.5em; 99 | box-shadow: 0 0px 5.3px rgb(115, 115, 115), 0 0px 5px rgb(253, 253, 253); 100 | } 101 | 102 | .spinning-loader { 103 | width: 20px; 104 | height: 20px; 105 | border: 5px solid rgb(6, 59, 4); 106 | border-left-color: wheat; 107 | border-radius: 50%; 108 | background: transparent; 109 | animation-name: rotate-s-loader; 110 | animation-iteration-count: infinite; 111 | animation-duration: 1s; 112 | animation-timing-function: linear; 113 | position: relative; 114 | visibility: visible; 115 | margin: 0 auto; 116 | } 117 | 118 | @keyframes rotate-s-loader { 119 | from { 120 | transform: rotate(0); 121 | } 122 | to { 123 | transform: rotate(360deg); 124 | } 125 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zmanim-node זמנים-נוד 2 | 3 |
4 | 5 | **אפליקציית נוד לבדיקת זמני ההלכה + אפשרות הרשמה לקבלת התראות פוש רבע שעה לפני שקיעה לפי ערים בישראל** 6 | 7 | האפליקציה (רצה על הרוקו Heroku): **https://chvsunset.herokuapp.com** 8 | 9 | ## API זמנים 10 | האפליקציה כוללת API לקבלת זמני היום לכל עיר בישראל. 11 | 12 | יש לגשת ל-URL הזה - https://chvsunset.herokuapp.com/api 13 |
14 | עם פרמטר של העיר הרצויה - https://chvsunset.herokuapp.com/api/?city=ירושלים 15 | 16 | בתשובה יתקבל JSON עם הנתונים (מה שמקבלים באתר כשעושים בדיקת זמנים - הנץ, קריאת שמע-גר"א, חצות ושקיעה) במבנה הזה: 17 |
18 | 19 | ``` 20 | { 21 | "cityname": "ירושלים", 22 | "netz": "05:34:51", 23 | "gra": "09:08:14", 24 | "chatzos": "12:41:36", 25 | "shkia": "19:48:22" 26 | } 27 | ``` 28 | 29 |
30 | 31 | דוגמא לשימוש ב-API בג'אווהסקריפט: 32 |
33 | 34 | ``` 35 | async function getzman(city) { 36 | const response = await fetch('https://chvsunset.herokuapp.com/api/?city=' + city) 37 | const data = await response.json() 38 | return data 39 | } 40 | ``` 41 | 42 |
43 | 44 | כעת נקרא לפונקציה ככה: 45 |
46 | 47 | ``` 48 | getzman('ירושלים') 49 | ``` 50 | 51 |
52 | 53 | לדוגמא אם רוצים לקבל את השקיעה - 54 |
55 | 56 | ``` 57 | let times = await getzman('ירושלים') 58 | console.log(times.shkia) 59 | ``` 60 | 61 |
62 | 63 | ### 📦 חבילות עיקריות בשימוש: 64 | * [kosher-zmanim](https://www.npmjs.com/package/kosher-zmanim) 65 | * [node-geocoder](https://www.npmjs.com/package/node-geocoder) 66 | * [moment](https://www.npmjs.com/package/moment) 67 | * [web-push](https://www.npmjs.com/package/web-push) 68 | 69 | 70 | > אפשר לראות את שלבי ה'פרוייקט' [כאן](https://tchumim.com/post/117654) ו[כאן](https://tchumim.com/post/116237)
71 | > (לכניסה לשני הקישורים יש להירשם לפורום תחומים + כניסה לקבוצת 'תיכנות') 72 | 73 | ### **אשמח להערות והארות!!** 74 | 75 | ## התקנה לוקלית 76 | כדי להפעיל אצלכם במחשב, הורידו את הריפו 77 |
78 | 79 | ``` 80 | git clone https://github.com/chaim-chv/zmanim-node.git && cd zmanim-node 81 | ``` 82 | 83 |
84 | 85 | והכניסו את המפתחות המתאימים בקובץ env (צריך להכניס את מפתחות ה-VAPID - מפיקים אותם בצורה פשוטה כמו שמוסבר [כאן](https://www.npmjs.com/package/web-push#command-line). חוץ מזה צריך להכניס גם כתובת URI של מסד נתונים MongoDB) 86 | 87 | ואז התקנה של החבילות הנצרכות ב-npm: 88 |
89 | 90 | ``` 91 | npm install 92 | ``` 93 | 94 |
95 | 96 | ואז 97 |
98 | 99 | ``` 100 | npm start 101 | ``` 102 | 103 |
104 | יפעיל שרת לוקאלי בפורט 5000 אצלכם (או שתגדירו אחרת). 105 | 106 | ## הפעלה בהרוקו 107 | **להתקנה מהירה בהרוקו** - לחצו על הכפתור והגדירו את המפתחות המתאימים במקומות הנכונים (יש תיאור לכל מפתח): 108 |
109 | 110 |
111 | 112 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/chaim-chv/zmanim-node/tree/master) 113 | 114 |
115 | 116 |
117 | אני לא ממליץ להתקין בהרוקו ישירות מהכפתור התקנה הזה (כיוון שזה יגרום לכך שלא תוכלו להוריד אחרי זה את הגיט של האפליקציה אליכם למחשב כדי לערוך) 118 |
119 | ממליץ לעבוד עם גיט מעיקרא, כך תוכלו לשנות ככל העולה על רוחכם (האפליקציה ששמתי בהרוקו שואבת את ה-branch שלה מכאן). 120 |
121 | ככה תעשו את זה: 122 |
123 | 124 | ``` 125 | git clone https://github.com/chaim-chv/zmanim-node.git && cd zmanim-node 126 | ``` 127 | 128 |
129 | 130 | תערכו אצלכם מה שאתם רוצים בקוד, ושימו לב שכל עריכה אתם מוסיפים לגיט. 131 | 132 | כעת צרו אפליקציה חדשה עם [Heroku-CLI](https://devcenter.heroku.com/articles/heroku-cli): 133 |
134 | 135 | ``` 136 | heroku create 137 | ``` 138 | 139 |
140 | 141 | ותדחפו אליה את התוכן: 142 |
143 | 144 | ``` 145 | git push heroku master 146 | ``` 147 | -------------------------------------------------------------------------------- /views/index.js: -------------------------------------------------------------------------------- 1 | if ('serviceWorker' in navigator) { 2 | navigator.serviceWorker.register('/sw.js', { 3 | scope: '/' 4 | }) 5 | } 6 | 7 | const publicVapidKey = 'BETFoC2Sxqx1JSpBeeXwL70kMp4Iy-nwl0pDiqGUHQzM4DWdu2HbFnkJewUoBNRLS9H2vuvBfg_LNYR4tVl5uKc' 8 | 9 | // Copied from the web-push documentation 10 | const urlBase64ToUint8Array = (base64String) => { 11 | const padding = '='.repeat((4 - (base64String.length % 4)) % 4) 12 | const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/') 13 | 14 | const rawData = window.atob(base64) 15 | const outputArray = new Uint8Array(rawData.length) 16 | 17 | for (let i = 0; i < rawData.length; ++i) { 18 | outputArray[i] = rawData.charCodeAt(i) 19 | } 20 | return outputArray 21 | } 22 | 23 | window.subscribe = async () => { 24 | document.getElementById('regrep').innerHTML = '
' 25 | if (!('serviceWorker' in navigator)) { 26 | console.log('no sw') 27 | return 28 | } 29 | 30 | const registration = await navigator.serviceWorker.ready 31 | 32 | // Subscribe to push notifications 33 | try { 34 | const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicVapidKey) }) 35 | const name = document.getElementById('nameee').value 36 | const city = document.getElementById('cityyy').value 37 | const response = await fetch('/subscribe', { method: 'POST', body: JSON.stringify({ subscription: subscription, name: name, city: city }), headers: { 'content-type': 'application/json;charset=utf-8' } }) 38 | if (response.status == 401) { 39 | document.getElementById('regrep').innerHTML = 'חובה לציין שם + עיר תקינה' 40 | } 41 | if (response.status == 201) { 42 | response.json().then((data) => { 43 | document.getElementById('regrep').innerHTML = 'נרשמת בהצלחה' 44 | document.getElementById('user').innerHTML = `שלום ${name}, אתה מקבל התראות על שקיעה ב${data.city}. להפסקת התראות` 45 | }) 46 | } 47 | } catch (e) { // טיפול במקרה שהיוזר לחץ לא לאשר התראות 48 | document.getElementById('regrep').innerHTML = 'תהליך ההרשמה עצר.. חובה לאשר קבלת התראות' 49 | console.log(`subscription stopped, error: ${e}`) 50 | } 51 | } 52 | 53 | fetch('/user', { headers: { 'content-type': 'application/json' }, credentials: 'include', method: 'POST' }).then((response) => { 54 | if (response.status == 404 || response.status == 401) { 55 | document.getElementById('user').innerHTML = 'משתמש: לא רשום לקבלת התראות מדפדפן זה' 56 | } else { 57 | response.json().then((data) => { 58 | if (data.stop == false) { 59 | console.log(data) 60 | document.getElementById('user').innerHTML = `שלום ${data.name}, אתה מקבל התראות על שקיעה ב${data.city}. להפסקת התראות` 61 | } else { 62 | console.log(data) 63 | document.getElementById('user').innerHTML = `שלום ${data.name}, נרשמת לקבל התראות על שקיעה ב${data.city}, והפסקת את ההתראות. להחזרת התראות` 64 | } 65 | }) 66 | } 67 | }) 68 | 69 | document.querySelector('#citt').addEventListener('keypress', function (e) { 70 | if (e.key === 'Enter') { 71 | getsun() 72 | } 73 | }) 74 | 75 | document.querySelector('#nameee').addEventListener('keypress', function (e) { 76 | if (e.key === 'Enter') { 77 | subscribe() 78 | } 79 | }) 80 | document.querySelector('#cityyy').addEventListener('keypress', function (e) { 81 | if (e.key === 'Enter') { 82 | subscribe() 83 | } 84 | }) 85 | 86 | function getsun () { 87 | document.getElementById('err').innerHTML = `
` 100 | document.getElementById('cityname').innerHTML = '' 101 | document.getElementById('netz').innerHTML = '' 102 | document.getElementById('gra').innerHTML = '' 103 | document.getElementById('chatzos').innerHTML = '' 104 | document.getElementById('shkia').innerHTML = '' 105 | const city = document.getElementById('citt').value 106 | fetch('/', { method: 'POST', body: JSON.stringify({ city: city }), headers: { 'content-type': 'application/json' } }).then((response) => { 107 | if (response.status == 404 || response.status == 401) { 108 | document.getElementById('err').innerHTML = 'לא הצלחנו למצוא כזו עיר' 109 | } else { 110 | response.json().then((data) => { 111 | console.log(data) 112 | document.getElementById('err').innerHTML = '' 113 | document.getElementById('cityname').innerHTML = `זמנים ל${data.city}:` 114 | document.getElementById('netz').innerHTML = `נץ בשעה ${data.zmanim.netz}\n` 115 | document.getElementById('gra').innerHTML = `זמן קריאת שמע (הגר"א) בשעה ${data.zmanim.gra}\n` 116 | document.getElementById('chatzos').innerHTML = `חצות בשעה ${data.zmanim.chatzos}\n` 117 | document.getElementById('shkia').innerHTML = `שקיעה בשעה ${data.zmanim.shkia}` 118 | }) 119 | } 120 | }) 121 | } 122 | 123 | async function stoppush () { 124 | const response = await fetch('/stop', { headers: { 'content-type': 'application/json' }, credentials: 'include', method: 'POST' }) 125 | if (response.status == 202) { 126 | document.getElementById('user').innerHTML = 'התראות הופסקו. ביטול' 127 | } else { 128 | // cant stop push 129 | } 130 | } 131 | 132 | async function startpush () { 133 | const response = await fetch('/start', { headers: { 'content-type': 'application/json' }, credentials: 'include', method: 'POST' }) 134 | if (response.status == 202) { 135 | document.getElementById('user').innerHTML = 'התראות הופעלו. ביטול' 136 | } else { 137 | // cant start push 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ path: 'variables.env' }) 2 | const express = require('express') 3 | const path = require('path') 4 | const cors = require('cors') 5 | const moment = require('moment') 6 | const fetch = require('node-fetch') 7 | const schedule = require('node-schedule') 8 | const cookieParser = require('cookie-parser') 9 | const KosherZmanim = require('kosher-zmanim') 10 | const NodeGeocoder = require('node-geocoder') 11 | const geocoder = NodeGeocoder({ provider: 'openstreetmap' }) 12 | const webPush = require('web-push') 13 | const MongoClient = require('mongodb').MongoClient 14 | const { ObjectId } = require('mongodb') 15 | const { resolveSoa } = require('dns') 16 | const app = express() 17 | const port = process.env.PORT || 5000 18 | 19 | const dburl = process.env.DBURL 20 | app.use(cookieParser()) 21 | 22 | app.set('view engine', 'ejs') 23 | app.use(express.json()) 24 | app.use(cors()) 25 | app.options('*', cors()) 26 | 27 | const publicVapidKey = process.env.PUBLIC_VAPID_KEY 28 | const privateVapidKey = process.env.PRIVATE_VAPID_KEY 29 | webPush.setVapidDetails('mailto:tmeemoot@gmail.com', publicVapidKey, privateVapidKey) 30 | 31 | const utcOffsetHours = '+03:00'; 32 | 33 | app.post("/subscribe", async function (req, res) { 34 | const subscription = req.body; 35 | if (!subscription.name || !subscription.city) { 36 | // בדיקה שהיוזר כתב שם ועיר 37 | res.status(401).json("name or city not declare"); 38 | } else { 39 | const locates = await geocoder.geocode(subscription.city); // ארבע השורות הבאות - בדיקה שהיוזר לא חירטט עיר 40 | if (!Array.isArray(locates) || !locates.length) { 41 | console.log("not an city"); 42 | return "nocity"; 43 | } else { 44 | const latitude = locates[0].latitude; 45 | const longitude = locates[0].longitude; 46 | const cityname = locates[0].city; 47 | if (req.cookies.userid) { 48 | const userID = req.cookies.userid; 49 | console.log(`user change notify settings but we find userid in cookies.. ${userID}`); 50 | const client = await MongoClient.connect(dburl, { useUnifiedTopology: true }); 51 | const record = await client.db("main").collection("users").findOneAndUpdate({ _id: ObjectId(userID) }, { $set: { name: subscription.name, city: subscription.city, lat: latitude, long: longitude } }); 52 | if (record) { 53 | console.log(`user options updated: name - ${subscription.name}, city - ${cityname}`); 54 | res.status(201).json({ city: cityname }); 55 | const payload = JSON.stringify({ title: "הגדרות ההתראה שלך השתנו בהצלחה", body: `נתריע לך רבע שעה לפני השקיעה ב${cityname}` }); 56 | webPush.sendNotification(subscription.subscription, payload).catch((error) => console.error(error)); 57 | } 58 | } else { 59 | const client = await MongoClient.connect(dburl, { useUnifiedTopology: true }); 60 | const userkeys = { name: subscription.name, city: cityname, lat: latitude, long: longitude, endpoint: subscription.subscription.endpoint, expiriationTime: subscription.subscription.expiriationTime, keys: { p256dh: subscription.subscription.keys.p256dh, auth: subscription.subscription.keys.auth }, stop: false }; 61 | const result = await client.db("main").collection("users").insertOne(userkeys); // רושמים את היוזר במסד נתונים 62 | const objid = result.insertedId; // מקבלים את המזהה של היוזר שנוצר במסד נתונים 63 | console.log(`userid is ${objid}, created succesfuly - name is ${subscription.name} and city is ${cityname}`); 64 | res.cookie("userid", objid, { expires: new Date("2022/01/20") }); // יוצרים עוגייה עם הערך של המזהה הנ"ל 65 | res.status(201).json({ city: cityname }); 66 | console.log("userid cookie created successfully"); 67 | const payload = JSON.stringify({ 68 | title: "נרשמת בהצלחה, להתראות לפני שקיעה", 69 | body: `נתריע לך רבע שעה לפני השקיעה ב${cityname}`, 70 | }); 71 | webPush.sendNotification(subscription.subscription, payload).catch((error) => console.error(error)); 72 | } 73 | } 74 | } 75 | }); 76 | 77 | app.use(express.static(path.join(__dirname, 'views'))) 78 | 79 | app.get('/', (req, res) => { 80 | res.render('index') 81 | }) 82 | 83 | app.get("/api", async function (req, res) { 84 | console.log(`new api call..`); 85 | let city = req.query.city; 86 | if (!city) { 87 | console.log(`city not declared`); 88 | res.status(400).json(`you have to pass cityname to the request..`); 89 | } else { 90 | const zmanim = await gettimesforcity(city); 91 | if (zmanim == "nocity") { 92 | res.status(400).json("city not found... 😥😥"); 93 | } else { 94 | res.status(200).json(zmanim); 95 | } 96 | } 97 | }); 98 | 99 | // כשהאתר עולה, פונקציה בפרונטאנד מבקשת מהשרת נתונים על המשתמש, לשורה העליונה של האתר 100 | app.post('/user', async function (req, res) { 101 | console.log('new visit, searching if theres a user cookie') 102 | if (req.cookies.userid) { 103 | const userID = req.cookies.userid // מזהים את היוזר לפי העוגייה שמכילה את המזהה שלו במסד נתונים 104 | console.log(`user cookie found with this id: ${userID}`) 105 | const client = await MongoClient.connect(dburl, { useUnifiedTopology: true }) 106 | const doc = await client.db('main').collection('users').findOne({ _id: ObjectId(userID) }) 107 | if (!doc) { 108 | console.log('id not found on DB') 109 | res.status(404).json('we cant find your userid in our DB... strange') 110 | } else { 111 | console.log('id match in DB. sending data to user') 112 | res.status(200).json({ name: doc.name, city: doc.city, stop: doc.stop }) 113 | } 114 | } else { 115 | res.status(404).json('no user cookie found') 116 | } 117 | }) 118 | 119 | // נותנים ליוזר לעשות פוס להתראות 120 | app.post('/stop', async function (req, res) { 121 | console.log(`user request to stop push. user id is ${req.cookies.userid}`) 122 | if (req.cookies.userid) { 123 | const userID = req.cookies.userid 124 | const client = await MongoClient.connect(dburl, { useUnifiedTopology: true }) 125 | const record = await client.db('main').collection('users').findOneAndUpdate({ _id: ObjectId(userID) }, { $set: { stop: true } }) 126 | if (!record) { 127 | console.log('id not found on DB') 128 | res.status(404).json('we cant find your userid in our DB... strange') 129 | } else { 130 | console.log('id match in DB. sending data to user') 131 | res.status(202).json('ok, push stopped for you') 132 | } 133 | } else { 134 | res.status(404).json('no user cookie found') 135 | } 136 | }) 137 | 138 | // ולבטל את הפוס 139 | app.post('/start', async function (req, res) { 140 | console.log(`user request to stop push. user id is ${req.cookies.userid}`) 141 | if (req.cookies.userid) { 142 | const userID = req.cookies.userid 143 | const client = await MongoClient.connect(dburl, { useUnifiedTopology: true }) 144 | const record = await client.db('main').collection('users').findOneAndUpdate({ _id: ObjectId(userID) }, { $set: { stop: false } }) 145 | if (!record) { 146 | console.log('id not found on DB') 147 | res.status(404).json('we cant find your userid in our DB... strange') 148 | } else { 149 | console.log('id match in DB. sending data to user') 150 | res.status(202).json('ok, push started back for you') 151 | } 152 | } else { 153 | res.status(404).json('no user cookie found') 154 | } 155 | }) 156 | 157 | app.post('/', async function (req, res) { 158 | console.log(`user check city manualy. his req is ${req.body.city}`) 159 | const zmanim = await gettimesforcity(req.body.city) 160 | if (zmanim == 'nocity') { 161 | res.status(404).json(`no city named ${req.body.city}`) 162 | } else { 163 | console.log(`got the answer... sending to user`) 164 | console.log(zmanim) 165 | res.status(200).json({ city: zmanim.cityname, zmanim: zmanim }) 166 | } 167 | }) 168 | 169 | app.listen(port, () => { 170 | console.log(`app started and listening at http://localhost:${port}`) 171 | }) 172 | 173 | async function push (id) { 174 | const client = await MongoClient.connect(dburl, { useUnifiedTopology: true }) 175 | const doc = await client.db('main').collection('users').findOne({ _id: ObjectId(id) }) 176 | let sunset = await gettimesforcity(doc.city) 177 | sunset = sunset.shkia 178 | sunset = sunset.toString() 179 | sunset = sunset.slice(16, 21) 180 | const payload = JSON.stringify({ 181 | title: 'תזכורת - שקיעת החמה', 182 | body: `שים לב! השקיעה ב${doc.city} עוד כרבע שעה\nבשעה ${sunset}\nהתפללת כבר מנחה? ותפילין 😉` 183 | }) 184 | const pushkeys = { endpoint: doc.endpoint, keys: { p256dh: doc.keys.p256dh, auth: doc.keys.auth } } 185 | webPush.sendNotification(pushkeys, payload).catch((error) => console.error(error)) 186 | } 187 | 188 | async function getsunforcity (citly) { 189 | const geolocation = await geocoder.geocode(citly) 190 | if (!Array.isArray(geolocation) || !geolocation.length) { 191 | console.log('not an city') 192 | return 'nocity' 193 | } else { 194 | const options = { 195 | date: Date.now(), 196 | timeZoneId: 'Asia/Jerusalem', 197 | locationName: geolocation[0].city, 198 | latitude: geolocation[0].latitude, 199 | longitude: geolocation[0].longitude, 200 | elevation: 0, 201 | complexZmanim: (boolean = false) 202 | } 203 | let sunset = KosherZmanim.getZmanimJson(options).BasicZmanim.Sunset 204 | sunset = new Date(sunset) 205 | sunset = moment(sunset).utcOffset(utcOffsetHours) 206 | return sunset 207 | } 208 | } 209 | 210 | async function gettimesforcity (citly) { 211 | const geolocation = await geocoder.geocode(citly) 212 | if (!Array.isArray(geolocation) || !geolocation.length) { 213 | console.log('not an city') 214 | return 'nocity' 215 | } else { 216 | const options = { 217 | date: Date.now(), 218 | timeZoneId: 'Asia/Jerusalem', 219 | locationName: geolocation[0].city, 220 | latitude: geolocation[0].latitude, 221 | longitude: geolocation[0].longitude, 222 | elevation: 0, 223 | complexZmanim: (boolean = false) 224 | } 225 | let netz = KosherZmanim.getZmanimJson(options).BasicZmanim.Sunrise 226 | let gra = KosherZmanim.getZmanimJson(options).BasicZmanim.SofZmanShmaGRA 227 | let chatzos = KosherZmanim.getZmanimJson(options).BasicZmanim.Chatzos 228 | let shkia = KosherZmanim.getZmanimJson(options).BasicZmanim.Sunset 229 | netz = new Date(netz) 230 | gra = new Date(gra) 231 | chatzos = new Date(chatzos) 232 | shkia = new Date(shkia) 233 | netz = moment(netz).utcOffset(utcOffsetHours) 234 | gra = moment(gra).utcOffset(utcOffsetHours) 235 | chatzos = moment(chatzos).utcOffset(utcOffsetHours) 236 | shkia = moment(shkia).utcOffset(utcOffsetHours) 237 | netz = moment(netz).format('kk:mm:ss') 238 | gra = moment(gra).format('kk:mm:ss') 239 | chatzos = moment(chatzos).format('kk:mm:ss') 240 | shkia = moment(shkia).format('kk:mm:ss') 241 | const zmanim = { cityname: geolocation[0].city, netz: netz, gra: gra, chatzos: chatzos, shkia: shkia } 242 | return zmanim 243 | } 244 | } 245 | 246 | async function checksun () { // חישוב הפושים לכל אחד מהיוזרים במסד נתונים, ויצירת טיימאאוטים לכל אחד 247 | console.log('started scheduled job - calculating pushes for everyone') 248 | const client = await MongoClient.connect(dburl, { useUnifiedTopology: true }) 249 | const result = await client.db('main').collection('users').find({}).toArray() 250 | console.log(`there is ${result.length} users in push DB:`) 251 | for (const user of result) { 252 | if (user.stop !== true) { 253 | const city = user.city 254 | const id = user._id 255 | const timeforcity = await getsunforcity(city) 256 | let thistime = new Date(Date.now()) 257 | thistime = moment(thistime).utcOffset(utcOffsetHours) 258 | let destime = moment.duration(timeforcity - thistime).asMilliseconds() 259 | destime = destime - 900000 // הורדה של רבע שעה 260 | if (destime < 0) { 261 | destime = destime + 86400000 262 | } 263 | console.log(`shkia for ${user.name} is in ${moment(timeforcity).format('kk:mm:ss')}, and now is ${moment(thistime).format('kk:mm:ss')}. remaining time in ms - ${destime}`) 264 | setTimeout(push, destime, id) 265 | } else { 266 | console.log(`userid ${user._id} requested to stop push...`) 267 | } 268 | } 269 | } 270 | 271 | schedule.scheduleJob('0 14 * * *', function () { checksun() }) // הרצה של חישוב הפושים לכולם, בשעה ארבע בצהריים של השרת 272 | --------------------------------------------------------------------------------