├── 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 |
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 |
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 | [](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 |
--------------------------------------------------------------------------------