├── nodemon.json.example
├── .idea
├── misc.xml
├── vcs.xml
├── modules.xml
└── notifications.iml
├── Dockerfile
├── server.js
├── mongoose.js
├── firebase.js
├── api
├── routes
│ ├── refreshToken.js
│ ├── sms.js
│ ├── subscribe.js
│ └── notifications.js
├── models
│ └── user.js
└── controllers
│ ├── refreshToken.js
│ ├── subscribe.js
│ ├── sms.js
│ └── notifications.js
├── serviceAccountKey.json.example
├── docker-compose.yml
├── package.json
├── test
├── refreshToken.js
├── sms.js
├── subscribe.js
└── notifications.js
├── app.js
├── README.md
└── .gitignore
/nodemon.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "MONGO_ATLAS_PW": ""
4 | }
5 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:10
2 |
3 | WORKDIR /app
4 |
5 | COPY package.json package.json
6 |
7 | RUN npm install
8 |
9 | COPY . .
10 |
11 | EXPOSE 3000
12 |
13 | RUN npm install -g nodemon
14 |
15 | CMD ["npm","start"]
16 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const app = require('./app');
3 | const port = process.env.port || 3000;
4 |
5 | const server = http.createServer(app);
6 |
7 | server.listen(port, function () {
8 | console.log(`Running on ${port}`);
9 | });
--------------------------------------------------------------------------------
/mongoose.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | mongoose.connect('mongodb+srv://mohamed1refaie:' + process.env.MONGO_ATLAS_PW + '@notifications-u3azq.mongodb.net/test?retryWrites=true', {
4 | useNewUrlParser: true
5 | });
6 |
7 | module.exports = mongoose;
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/firebase.js:
--------------------------------------------------------------------------------
1 | let admin = require("firebase-admin");
2 |
3 | let serviceAccount = require("./serviceAccountKey");
4 |
5 | admin.initializeApp({
6 | credential: admin.credential.cert(serviceAccount),
7 | databaseURL: "https://notificationsdemoapp-a1f6a.firebaseio.com"
8 | });
9 |
10 |
11 | module.exports = admin;
--------------------------------------------------------------------------------
/api/routes/refreshToken.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const RefreshTokenController = require('../controllers/refreshToken');
5 |
6 | /*
7 | PUT Request that takes id and the new token for the user
8 | id: string ,required,
9 | token: string, required
10 | */
11 | router.put('/', RefreshTokenController.refresh_user_token);
12 |
13 |
14 | module.exports = router;
--------------------------------------------------------------------------------
/serviceAccountKey.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "type": "service_account",
3 | "project_id": "",
4 | "private_key_id": "",
5 | "private_key": "",
6 | "client_email": "",
7 | "client_id": "",
8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9 | "token_uri": "https://oauth2.googleapis.com/token",
10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11 | "client_x509_cert_url": ""
12 | }
13 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | app:
4 | container_name: notification-service
5 | restart: always
6 | build:
7 | context: ./
8 | args:
9 | port: "3000"
10 | ports:
11 | - "3000:3000"
12 | volumes:
13 | - .:/app
14 | - /app/node_modules
15 | links:
16 | - mongo
17 | mongo:
18 | container_name: mongo
19 | image: mongo
20 | ports:
21 | - "27017:27017"
22 |
--------------------------------------------------------------------------------
/.idea/notifications.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/api/models/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | /*
4 | _id : mongoose primary key
5 | token : (the firebase cloud messaging -fcm- token)
6 | phoneNumber : the user's phone number
7 | userdId : the user's Id in the original Database (foreign key)
8 | email : the user's email
9 |
10 | */
11 | const userSchema = mongoose.Schema({
12 | _id: mongoose.Schema.Types.ObjectId,
13 | token: {type: String, required: true},
14 | phoneNumber: {type: String, required: true},
15 | userId: {type: String, required: true},
16 | email: String
17 | });
18 |
19 | module.exports = mongoose.model('User', userSchema);
--------------------------------------------------------------------------------
/api/routes/sms.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const SMSController = require('../controllers/sms');
4 |
5 | /*
6 | Post request that sends SMS to all users,
7 | It takes message in the request body.
8 | message : string, required
9 | */
10 | router.post('/toAll', SMSController.send_sms_to_all);
11 |
12 |
13 | /*
14 | Post request that sends SMS to a specific user or users,
15 | It takes message and ids in the request body.
16 | message: string, required
17 | ids: string or array of strings, required
18 | */
19 | router.post('/', SMSController.send_sms_to_specific);
20 |
21 | module.exports = router;
--------------------------------------------------------------------------------
/api/routes/subscribe.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const subscribeController = require("../controllers/subscribe");
5 |
6 | /*
7 | Post request that takes the user's info and saves
8 | the info.
9 | email : string,required,
10 | phoneNumber : string, required,
11 | token: (fcm token) string,required,
12 | userId: string,required
13 | */
14 | router.post('/', subscribeController.subscribe);
15 |
16 |
17 | /*
18 | Post request that takes the topic of the subscription
19 | in the params and ids in the body
20 | ids: string or array of strings, required
21 | */
22 | router.post('/:topic', subscribeController.subscribe_to_topic);
23 |
24 |
25 | module.exports = router;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notifications",
3 | "version": "1.0.0",
4 | "description": "A node.js restful API for notifications",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha --timeout 10000",
8 | "start": "nodemon server.js"
9 | },
10 | "keywords": [
11 | "node",
12 | "restful",
13 | "api"
14 | ],
15 | "author": "Mohamed Refaie",
16 | "license": "ISC",
17 | "dependencies": {
18 | "body-parser": "^1.19.0",
19 | "express": "^4.16.4",
20 | "firebase": "^5.11.0",
21 | "firebase-admin": "^7.3.0",
22 | "mongoose": "^5.5.5",
23 | "morgan": "^1.9.1"
24 | },
25 | "devDependencies": {
26 | "nodemon": "^1.18.11",
27 | "chai": "^3.5.0",
28 | "chai-http": "^2.0.1",
29 | "mocha": "^2.4.5"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/api/controllers/refreshToken.js:
--------------------------------------------------------------------------------
1 | const User = require('../models/user');
2 |
3 |
4 | /*
5 | refresh_user_token function updates the token of the user
6 | given the id and the new token. It extracts the id and token
7 | from the request body then updates the token of the associated user
8 | */
9 | exports.refresh_user_token = (req, res, next) => {
10 |
11 | if (req.body.id === undefined || req.body.token === undefined || req.body.id === "" || req.body.token === "") {
12 | res.status(400).json({error: "id and token are required"});
13 | } else {
14 | const id = req.body.id;
15 | const newToken = req.body.token;
16 | User.update({userId: id}, {$set: {token: newToken}}).exec().then(response => {
17 | res.status(200).json(response);
18 | }).catch(err => {
19 | res.status(500).json({error: err});
20 | }
21 | );
22 | }
23 | };
--------------------------------------------------------------------------------
/test/refreshToken.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'test';
2 |
3 | const User = require('../api/models/user');
4 |
5 | let chai = require('chai');
6 | let chaiHttp = require('chai-http');
7 | let app = require('../app');
8 | let should = chai.should();
9 |
10 |
11 | chai.use(chaiHttp);
12 |
13 | //Our parent block
14 | describe('Refresh Token', () => {
15 |
16 | /*
17 | * Test the /PUT refreshToken route
18 | */
19 | describe('Update user token', () => {
20 | it('it should not update the user without a token field', (done) => {
21 | let data = {
22 | id: "73",
23 | };
24 | chai.request(app)
25 | .put('/refreshToken')
26 | .send(data)
27 | .end((err, res) => {
28 | res.should.have.status(400);
29 | res.body.should.be.a('object');
30 | res.body.should.have.property('error').eql("id and token are required");
31 | done();
32 | });
33 | });
34 |
35 | });
36 |
37 |
38 | });
39 |
40 |
--------------------------------------------------------------------------------
/api/routes/notifications.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const NotificationsController = require('../controllers/notifications');
5 |
6 |
7 | /*
8 | Post request that send Notification for all users,
9 | It takes title, message and data in the request body.
10 | title: string, required,
11 | message: string, required,
12 | data: object, required
13 | */
14 | router.post('/toAll', NotificationsController.send_notification_to_all);
15 |
16 |
17 | /*
18 | Post request that send Notification for a specific user or users
19 | It takes ids, title, message, and data in the request body.
20 | ids: string or array of strings, required,
21 | title: string, required,
22 | message: string, required,
23 | data: object, required
24 | */
25 | router.post('/', NotificationsController.send_notification_to_specific);
26 |
27 |
28 | /*
29 | Post request that send Notification for a specific group with a common topic
30 | It takes topic, title, message, and data in the request body.
31 | topic: string, required,
32 | title: string, required,
33 | message: string, required,
34 | data: object, required
35 | */
36 | router.post('/toGroup', NotificationsController.send_notification_to_group);
37 |
38 | module.exports = router;
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const app = express();
3 | const morgan = require('morgan');
4 | const bodyParser = require('body-parser');
5 | const mongoose = require('./mongoose');
6 | const notificationsRoutes = require('./api/routes/notifications');
7 | const smsRoutes = require('./api/routes/sms');
8 | const subscribeRoutes = require('./api/routes/subscribe');
9 | const refreshTokenRoutes = require('./api/routes/refreshToken');
10 |
11 | app.use(morgan('dev'));
12 | app.use(bodyParser.urlencoded({extended: false}));
13 | app.use(bodyParser.json());
14 |
15 | app.use((req, res, next) => {
16 | res.header('Access-Control-Allow-Origin', '*');
17 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
18 | if (req.method === 'OPTIONS') {
19 | res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET');
20 | return res.status(200).json({});
21 | }
22 | next();
23 | });
24 |
25 | //Routes which handle the requests
26 | app.use('/notifications', notificationsRoutes);
27 | app.use('/sms', smsRoutes);
28 | app.use('/subscribe', subscribeRoutes);
29 | app.use('/refreshToken', refreshTokenRoutes);
30 |
31 | app.use((req, res, next) => {
32 | const error = new Error("not found");
33 | error.status = 404;
34 | next(error);
35 | });
36 |
37 | app.use((error, req, res, next) => {
38 | res.status(error.status || 500);
39 | res.json({
40 | error: {
41 | message: error.message
42 | }
43 | })
44 | });
45 |
46 | module.exports = app;
--------------------------------------------------------------------------------
/test/sms.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'test';
2 |
3 | const User = require('../api/models/user');
4 |
5 | let chai = require('chai');
6 | let chaiHttp = require('chai-http');
7 | let app = require('../app');
8 | let should = chai.should();
9 |
10 |
11 | chai.use(chaiHttp);
12 |
13 | //Our parent block
14 | describe('SMS', () => {
15 |
16 | /*
17 | * Test the /POST sms/toAll route
18 | */
19 | describe('SMS to all users', () => {
20 | it('it should not send sms without a message field', (done) => {
21 | let sms = {};
22 | chai.request(app)
23 | .post('/sms/toAll')
24 | .send(sms)
25 | .end((err, res) => {
26 | res.should.have.status(400);
27 | res.body.should.be.a('object');
28 | res.body.should.have.property('error').eql("message is required");
29 | done();
30 | });
31 | });
32 |
33 | });
34 |
35 | /*
36 | * Test the /POST sms/ route
37 | */
38 | describe('SMS to a specific user or users', () => {
39 | it('it should not send sms without an ids field', (done) => {
40 | let sms = {
41 | message: "Dummy message"
42 | };
43 | chai.request(app)
44 | .post('/sms')
45 | .send(sms)
46 | .end((err, res) => {
47 | res.should.have.status(400);
48 | res.body.should.be.a('object');
49 | res.body.should.have.property('error').eql('message and ids are required');
50 | done()
51 | });
52 | });
53 | });
54 |
55 |
56 | });
57 |
58 |
--------------------------------------------------------------------------------
/test/subscribe.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'test';
2 |
3 | const User = require('../api/models/user');
4 |
5 | let chai = require('chai');
6 | let chaiHttp = require('chai-http');
7 | let app = require('../app');
8 | let should = chai.should();
9 |
10 |
11 | chai.use(chaiHttp);
12 |
13 | //Our parent block
14 | describe('Subscribe', () => {
15 |
16 | /*
17 | * Test the /POST subscribe/route
18 | */
19 | describe('Subscribe the user', () => {
20 | it('it should not save a user without userId', (done) => {
21 | let user = {
22 | token: "4555",
23 | phoneNumber: "+201111111111",
24 | email: "mohamed1refaie@hotmail.com"
25 | };
26 | chai.request(app)
27 | .post('/subscribe')
28 | .send(user)
29 | .end((err, res) => {
30 | res.should.have.status(401);
31 | res.body.should.be.a('object');
32 | res.body.should.have.property('errors');
33 | res.body.errors.should.have.property('userId');
34 | res.body.errors.userId.should.have.property('message');
35 | res.body.errors.userId.should.have.property('message').eql('Path `userId` is required.');
36 | done();
37 | });
38 | });
39 |
40 | });
41 |
42 | /*
43 | * Test the /POST subscribe/:topic route
44 | */
45 | describe('Subscribe the user or users to a topic', () => {
46 | it('it should not make a subscription to a topic without an ids field', (done) => {
47 | let object = {};
48 | chai.request(app)
49 | .post('/subscribe/sports')
50 | .send(object)
51 | .end((err, res) => {
52 | res.should.have.status(400);
53 | res.body.should.be.a('object');
54 | res.body.should.have.property('error').eql('ids field is required');
55 | done()
56 | });
57 | });
58 | });
59 |
60 |
61 | });
62 |
63 |
--------------------------------------------------------------------------------
/api/controllers/subscribe.js:
--------------------------------------------------------------------------------
1 | const User = require('../models/user');
2 | const admin = require('../../firebase');
3 | const mongoose = require('mongoose');
4 |
5 | /*
6 | subscribe function extracts the user's info
7 | from the request body ,creates a new user
8 | and try to save the user in the database
9 | */
10 | exports.subscribe = (req, res, next) => {
11 |
12 | const user = new User({
13 | _id: new mongoose.Types.ObjectId(),
14 | token: req.body.token,
15 | phoneNumber: req.body.phoneNumber,
16 | userId: req.body.userId,
17 | email: req.body.email
18 | });
19 |
20 | user.save().then(result => {
21 | res.status(201).json(result)
22 | }).catch(err => {
23 | res.status(401).json(err);
24 | }
25 | );
26 |
27 | };
28 |
29 |
30 | /*
31 | subscribe_to_topic function extracts the topic of subscription
32 | from request params , the Ids string or array of strings from
33 | request body. Then find the users with those Ids, get their
34 | tokens and subscribe the tokens to the topic
35 | */
36 | exports.subscribe_to_topic = (req, res, next) => {
37 | const topic = req.params.topic;
38 | const Ids = req.body.ids;
39 | if (Ids === undefined || Ids === "") {
40 | res.status(400).json({error: "ids field is required"});
41 | }
42 | User.find({'userId': Ids}).select('token -_id').then(users => {
43 | let tokens = users.map((user) => {
44 | return user.token;
45 | });
46 | if (tokens.length > 0) {
47 | admin.messaging().subscribeToTopic(tokens, topic)
48 | .then(function (response) {
49 | res.status(200).json(response);
50 | })
51 | .catch(function (error) {
52 | res.status(500).json(error);
53 | });
54 | } else {
55 | res.status(400).json({error: "not valid Ids"});
56 | }
57 |
58 | }).catch(err => {
59 | console.log(err);
60 | res.status(500).json({
61 | error: err,
62 | });
63 | });
64 |
65 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notification Service
2 |
3 | This is a Notification service build with [nodejs](https://nodejs.org/en/), it uses [express](https://expressjs.com/), [mongoose](https://mongoosejs.com/) and [firebase-admin sdk](https://firebase.google.com/docs/admin/setup) to send notifications. It let you
4 | 1. subscribe a user to the notifications, subscribe a user to a certain topic.
5 | 2. send notification to all the subscribed users, to a specific user or users, to a group of users with a common topic.
6 | 3. send sms to all the subscribed users, to a specific user or users. (you have to integrate with an sms provider)
7 | 4. refresh a certain user's firebase token.
8 |
9 | ## Instructions
10 |
11 | To Run the Project:
12 | * clone the repo or download it
13 | * cd into the directory of the project from the terminal
14 | * head to the [Firebase console](https://console.firebase.google.com/u/0/)
15 | * create a new project from the firebase console and give it a name, or select an existing project.
16 | * go to your project's settings, then to service accounts, then generate new private key and download it.
17 | * rename `serviceAccountKey.json.example` to `serviceAccountKey.json`
18 | * copy the content of the private key file you just downloaded into `serviceAccountKey.json`
19 | * configure your mongo database in `mongoose.js` in my case i use a free cluster at [MongoDB Atlas](https://www.mongodb.com/cloud/atlas), and it's password will be in `nodemon.json.example` then rename it to `nodemon.json`
20 | * install all project dependencies with `npm install`
21 | * run the project with `npm start` and it will be listening at localhost:3000
22 | * test the project with `npm test`
23 |
24 | ***To Run the Service with Docker:***
25 | * make sure that docker and docker-compose are installed.
26 | * run `sudo docker-compose up` it will be listening at localhost:3000
27 |
28 | The API contains 8 endpoints
29 | **You can see a full Documentation for the API and examples from [here](https://documenter.getpostman.com/view/3845720/S1Lwy7kT)**
30 |
31 | 1. /subscribe
32 | * /
33 | * /:topic
34 | 2. /notifications
35 | * /
36 | * /notifications/toAll
37 | * /notifications/toGroup
38 | 3. /sms
39 | * /
40 | * /toAll
41 | 4. /refreshToken
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/test/notifications.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'test';
2 |
3 | const User = require('../api/models/user');
4 |
5 | let chai = require('chai');
6 | let chaiHttp = require('chai-http');
7 | let app = require('../app');
8 | let should = chai.should();
9 |
10 |
11 | chai.use(chaiHttp);
12 |
13 | //Our parent block
14 | describe('Notifications', () => {
15 |
16 | /*
17 | * Test the /POST notifications/toAll route
18 | */
19 | describe('Notification to All users', () => {
20 | it('it should not send a notification without a title field', (done) => {
21 | let notification = {
22 | message: "Test Notification body",
23 | data: {}
24 | };
25 | chai.request(app)
26 | .post('/notifications/toAll')
27 | .send(notification)
28 | .end((err, res) => {
29 | res.should.have.status(400);
30 | res.body.should.be.a('object');
31 | res.body.should.have.property('error').eql("title, message, and data are required");
32 | done();
33 | });
34 | });
35 |
36 | });
37 |
38 | /*
39 | * Test the /POST notifications/ route
40 | */
41 | describe('Notification to a specific user or users', () => {
42 | it('it should not send a notification without an ids field', (done) => {
43 | let notifcation = {
44 | title: "Dummy title",
45 | message: "Dummy message",
46 | data: {}
47 | };
48 | chai.request(app)
49 | .post('/notifications')
50 | .send(notifcation)
51 | .end((err, res) => {
52 | res.should.have.status(400);
53 | res.body.should.be.a('object');
54 | res.body.should.have.property('error').eql('ids, title, message, and data are required');
55 | done()
56 | });
57 | });
58 | });
59 |
60 |
61 | /*
62 | * Test the /POST notifications/toGroup route
63 | */
64 | describe('Notification to a specific group', () => {
65 | it('it should not send a notification without an topic field', (done) => {
66 | let notifcation = {
67 | title: "Dummy title",
68 | message: "Dummy message",
69 | data: {}
70 | };
71 | chai.request(app)
72 | .post('/notifications/toGroup')
73 | .send(notifcation)
74 | .end((err, res) => {
75 | res.should.have.status(400);
76 | res.body.should.be.a('object');
77 | res.body.should.have.property('error').eql('topic, title, message, and data are required');
78 | done()
79 | });
80 | });
81 | });
82 |
83 |
84 | });
85 |
86 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (https://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 | .env.test
64 |
65 | # parcel-bundler cache (https://parceljs.org/)
66 | .cache
67 |
68 | # next.js build output
69 | .next
70 |
71 | # nuxt.js build output
72 | .nuxt
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless/
79 |
80 | # FuseBox cache
81 | .fusebox/
82 |
83 |
84 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
85 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
86 |
87 | # User-specific stuff
88 | .idea/**/workspace.xml
89 | .idea/**/tasks.xml
90 | .idea/**/usage.statistics.xml
91 | .idea/**/dictionaries
92 | .idea/**/shelf
93 |
94 | # Generated files
95 | .idea/**/contentModel.xml
96 |
97 | # Sensitive or high-churn files
98 | .idea/**/dataSources/
99 | .idea/**/dataSources.ids
100 | .idea/**/dataSources.local.xml
101 | .idea/**/sqlDataSources.xml
102 | .idea/**/dynamic.xml
103 | .idea/**/uiDesigner.xml
104 | .idea/**/dbnavigator.xml
105 |
106 | # Gradle
107 | .idea/**/gradle.xml
108 | .idea/**/libraries
109 |
110 | # Gradle and Maven with auto-import
111 | # When using Gradle or Maven with auto-import, you should exclude module files,
112 | # since they will be recreated, and may cause churn. Uncomment if using
113 | # auto-import.
114 | # .idea/modules.xml
115 | # .idea/*.iml
116 | # .idea/modules
117 |
118 | # CMake
119 | cmake-build-*/
120 |
121 | # Mongo Explorer plugin
122 | .idea/**/mongoSettings.xml
123 |
124 | # File-based project format
125 | *.iws
126 |
127 | # IntelliJ
128 | out/
129 |
130 | # mpeltonen/sbt-idea plugin
131 | .idea_modules/
132 |
133 | # JIRA plugin
134 | atlassian-ide-plugin.xml
135 |
136 | # Cursive Clojure plugin
137 | .idea/replstate.xml
138 |
139 | # Crashlytics plugin (for Android Studio and IntelliJ)
140 | com_crashlytics_export_strings.xml
141 | crashlytics.properties
142 | crashlytics-build.properties
143 | fabric.properties
144 |
145 | # Editor-based Rest Client
146 | .idea/httpRequests
147 |
148 | # Android studio 3.1+ serialized cache file
149 | .idea/caches/build_file_checksums.ser
150 |
151 | # DynamoDB Local files
152 | .dynamodb/
153 |
154 |
155 | serviceAccountKey.json
156 | nodemon.json
--------------------------------------------------------------------------------
/api/controllers/sms.js:
--------------------------------------------------------------------------------
1 | const User = require('../models/user');
2 |
3 |
4 | /*
5 | send_sms function receives the phoneNumbers and
6 | the message and returns a promise
7 | TODO: INTEGRATE WITH A REAL SMS PROVIDER
8 | */
9 | let send_sms = (phoneNumbers, message) => {
10 |
11 | return new Promise(function (resolve, reject) {
12 | // integrate with an sms provider
13 | resolve({message: "success"});
14 |
15 | })
16 |
17 | };
18 |
19 |
20 | /*
21 | send_sms_to_all function sends sms to all users,
22 | it extracts the message from the request body,
23 | gets the phone numbers for all users,
24 | divide the phone numbers in loads that sms provider
25 | can handle in one minute, iterate over the loads and
26 | send sms to each load, wait a minute and send again.
27 | */
28 | exports.send_sms_to_all = (req, res, next) => {
29 |
30 | const msg = req.body.message;
31 | if (msg === undefined || msg === "") {
32 | res.status(400).json({error: "message is required"})
33 | } else {
34 | User.find().exec().then(users => {
35 |
36 | let phoneNumbers = users.map((user) => {
37 | return user.phoneNumber;
38 | });
39 |
40 | if (phoneNumbers.length > 0) {
41 | let returnJson = [];
42 | let statusCode = 200;
43 | let maximumCapacity = 1000;
44 | let noOfLoads = phoneNumbers.length / maximumCapacity;
45 | if (phoneNumbers.length % maximumCapacity !== 0) {
46 | noOfLoads++;
47 | }
48 | noOfLoads = Math.floor(noOfLoads);
49 |
50 | for (let i = 0; i < noOfLoads; i++) {
51 | let start = i * maximumCapacity, end = (i + 1) * maximumCapacity;
52 | let batchPhoneNumbers = phoneNumbers.slice(start, end);
53 | send_sms(batchPhoneNumbers, msg).then(jsonObj => {
54 | returnJson.push({loadNumber: i + 1, success: jsonObj});
55 | if (i + 1 === noOfLoads) {
56 | res.status(statusCode).json(returnJson);
57 | }
58 | }).catch(error => {
59 | returnJson.push({loadNumber: i + 1, error: error});
60 | statusCode = 500;
61 |
62 | if (i + 1 === noOfLoads) {
63 | res.status(statusCode).json(returnJson);
64 | }
65 | });
66 |
67 | setTimeout(() => {
68 |
69 | }, 60000);
70 |
71 | }
72 | } else {
73 | res.status(200).json({message: "no phone numbers to send to"})
74 | }
75 |
76 |
77 | }).catch(err => {
78 | console.log(err);
79 | res.status(500).json({
80 | error: err,
81 | });
82 | });
83 | }
84 | };
85 |
86 |
87 | /*
88 | send_sms_to_specific function sends sms to a specific
89 | user or users, it extracts the message and ids from
90 | the request body, get the phone numbers associated
91 | with those ids then send sms to them
92 | */
93 | exports.send_sms_to_specific = (req, res, next) => {
94 | const msg = req.body.message;
95 | const Ids = req.body.ids;
96 |
97 | if (msg === undefined || msg === "" || Ids === undefined) {
98 | res.status(400).json({error: "message and ids are required"});
99 | } else {
100 | User.find({'userId': Ids}).select('phoneNumber -_id').then(users => {
101 | let phoneNumbers = users.map((user) => {
102 | return user.phoneNumber;
103 | });
104 |
105 | if (phoneNumbers.length > 0) {
106 | send_sms(phoneNumbers, msg).then(jsonObj => {
107 | res.status(200).json(jsonObj);
108 | }).catch(error => {
109 | res.status(500).json(error);
110 | })
111 | } else {
112 | res.status(200).json({message: "No valid ids"});
113 | }
114 |
115 | }).catch(err => {
116 | console.log(err);
117 | res.status(500).json({
118 | error: err,
119 | });
120 | });
121 | }
122 | };
--------------------------------------------------------------------------------
/api/controllers/notifications.js:
--------------------------------------------------------------------------------
1 | const User = require('../models/user');
2 | const admin = require('../../firebase');
3 |
4 |
5 | /*
6 | send_notification function sends notification for
7 | the given tokens with the given payload. it receives
8 | the tokens and payload then return a promise, it tries
9 | to send notification to the tokens with fcm admin sdk
10 | */
11 | let send_notification = (tokens, payload) => {
12 |
13 | let options = {
14 | priority: "normal",
15 | timeToLive: 60 * 60
16 | };
17 | return new Promise(function (resolve, reject) {
18 | admin.messaging().sendToDevice(tokens, payload, options)
19 | .then(function (response) {
20 | resolve({message: response});
21 |
22 | })
23 | .catch(function (error) {
24 | console.log("Error sending message:", error);
25 | reject({error: error});
26 | });
27 | })
28 |
29 | };
30 |
31 |
32 | /*
33 | send_notification_to_all function sends a notification
34 | to all users. it extracts the message,title and data from
35 | the request body. initialize the payload, gets the tokens
36 | of all users, divide the tokens in loads of 1000's (maximum
37 | number of tokens fcm can handle in one request. then sends
38 | notification for each load.
39 | */
40 | exports.send_notification_to_all = (req, res, next) => {
41 |
42 | const title = req.body.title;
43 | const msg = req.body.message;
44 | const data = req.body.data;
45 | if (msg === undefined || msg === "" || title === undefined || title === "" || data === undefined) {
46 | res.status(400).json({error: "title, message, and data are required"});
47 | } else {
48 | let payload = {
49 | notification: {
50 | title: title,
51 | body: msg
52 | },
53 | data: data
54 | };
55 |
56 | User.find().exec().then(users => {
57 |
58 | let tokens = users.map((user) => {
59 | return user.token;
60 | });
61 |
62 | if (tokens.length > 0) {
63 | let returnJson = [];
64 | let statusCode = 200;
65 | let noOfLoads = tokens.length / 1000;
66 | if (tokens.length % 1000 !== 0) {
67 | noOfLoads++;
68 | }
69 | noOfLoads = Math.floor(noOfLoads);
70 |
71 | for (let i = 0; i < noOfLoads; i++) {
72 | let start = i * 1000, end = (i + 1) * 1000;
73 | let batchTokens = tokens.slice(start, end);
74 | send_notification(batchTokens, payload).then(jsonObj => {
75 | returnJson.push({loadNumber: i + 1, success: jsonObj});
76 | if (i + 1 === noOfLoads) {
77 | res.status(statusCode).json(returnJson);
78 | }
79 | }).catch(error => {
80 | returnJson.push({loadNumber: i + 1, error: error});
81 | statusCode = 500;
82 |
83 | if (i + 1 === noOfLoads) {
84 | res.status(statusCode).json(returnJson);
85 | }
86 | });
87 | }
88 | } else {
89 | res.status(200).json({message: "no tokens to send to"})
90 | }
91 |
92 |
93 | }).catch(err => {
94 | console.log(err);
95 | res.status(500).json({
96 | error: err,
97 | });
98 | });
99 | }
100 | };
101 |
102 | /*
103 | send_notification_to_specific function send a notification
104 | to a specific user or users. it extracts the ids, message,
105 | title and data from the request body. Initialize the payload,
106 | get the users tokens then sends the notification to them.
107 | */
108 | exports.send_notification_to_specific = (req, res, next) => {
109 | const Ids = req.body.ids;
110 | const title = req.body.title;
111 | const msg = req.body.message;
112 | const data = req.body.data;
113 |
114 | if (Ids === undefined || msg === undefined || title === undefined || data === undefined || msg === "" || title === "") {
115 | res.status(400).json({error: "ids, title, message, and data are required"});
116 | } else {
117 | let payload = {
118 | notification: {
119 | title: title,
120 | body: msg
121 | },
122 | data: data
123 | };
124 |
125 | User.find({'userId': Ids}).select('token -_id').then(users => {
126 | let tokens = users.map((user) => {
127 | return user.token;
128 | });
129 |
130 | if (tokens.length > 0) {
131 | send_notification(tokens, payload).then(jsonObj => {
132 | res.status(200).json(jsonObj);
133 | }).catch(error => {
134 | res.status(500).json(error);
135 | })
136 | } else {
137 | res.status(200).json({message: "No valid ids"});
138 | }
139 |
140 | }).catch(err => {
141 | console.log(err);
142 | res.status(500).json({
143 | error: err,
144 | });
145 | });
146 | }
147 |
148 | };
149 |
150 |
151 | /*
152 | send_notification_to_group function sends notification
153 | to a specific group with a common topic. it extracts the
154 | topic, message, title and data from the request body.
155 | Initialize the the payload and sends the notification for
156 | the users subscribed to the given topic.
157 | */
158 | exports.send_notification_to_group = (req, res, next) => {
159 | const topic = req.body.topic;
160 | const title = req.body.title;
161 | const msg = req.body.message;
162 | const data = req.body.data;
163 |
164 | if (topic === undefined || topic === "" || msg === undefined || title === undefined || data === undefined || msg === "" || title === "") {
165 | res.status(400).json({error: "topic, title, message, and data are required"});
166 | } else {
167 |
168 | let payload = {
169 | notification: {
170 | title: title,
171 | body: msg
172 | },
173 | data: data
174 | };
175 |
176 | admin.messaging().sendToTopic(topic, payload)
177 | .then(function (response) {
178 | res.status(200).json(response);
179 | })
180 | .catch(function (error) {
181 | res.status(500).json(error);
182 | });
183 | }
184 | };
185 |
--------------------------------------------------------------------------------