Compare commits

..

No commits in common. "master" and "no-orm-incomplete" have entirely different histories.

41 changed files with 918 additions and 4288 deletions

View File

@ -2,65 +2,10 @@
Meeting Planner helps you find the best time to schedule a meeting across several timezones.
It's based on the availibity of all participants for specific days.
- [Documentation](https://git.ruihildt.xyz/meeting-planner/documentation)
- [Frontend](https://git.ruihildt.xyz/meeting-planner/frontend)
---
**URL** https://meeting-planner-backend.herokuapp.com/
---
- [Documentation](https://git.armada.digital/meeting-planner/documentation)
- Frontend (coming soon)
# Backend architecture
- [API endpoints](https://git.ruihildt.xyz/meeting-planner/documentation/src/branch/master/api-documentation.md)
- [API endpoints](https://git.armada.digital/meeting-planner/documentation/src/branch/master/api-documentation.md)
- [Database design](https://dbdiagram.io/d/5e769ab14495b02c3b88936f)
- [Flowchart](https://app.diagrams.net/#Uhttps://git.ruihildt.xyz/meeting-planner/documentation/raw/branch/master/meetingscheduler.drawio)
## Installation
### 0 | Requirements
Meeting Planner runs on virtually any system where Node.js is supported.
This means it runs on Linux, macOS, Windows as well as container solutions such as Docker / Kubernetes and Heroku.
**Versions**
- Postgres 9.5 or later
- NodeJS 10.12 or later
*[KnexJS](https://knexjs.org/), the database driver used is compatible with other databases like MySQL.
With small adaptation, it should be possible to use them, but this has not been tested.*
### 1 | Configure Postgres to use UTC
** Make sure that Postgres database used is configured to UTC **
`SET TIME ZONE 'UTC';`
See [Postgres Documentation](https://www.postgresql.org/docs/current/sql-set.html)
### 2 | Create and/or configure environement variables
- Create a `.env` file at the root of the backend folder
- Replace all variables noted with `$` below, and save it to the `.env` file
```
NODE_ENV=development // This can't be changed for now
PORT=$port_number
DATABASE_URL=postgres://$db_user:$db_user_password@$hostname:5432/$db_name
JWT_SECRET=$long_random_characters
```
### 3 | Installation of packages with npm
- `npm i`
### 4 | Migrate tables to database
- `npx knex migrate:latest`
### 5 | Seed database with dummy content **(optional)**
Running this will add some accounts along with some meetings.
- `npx knex seed:run`
**Accounts created**
| Liza | Emile | Jack | Cynthia | Celine |
| :--------------- | :---------------- | :--------------- | :------------------ | :----------------- |
| liza@example.com | emile@example.com | jack@example.com | cynthia@example.com | celine@example.com |
| password | password | password | password | password |
- [Flowchart](https://app.diagrams.net/#Uhttps://git.armada.digital/meeting-planner/documentation/raw/branch/master/meetingscheduler.drawio)

View File

@ -1,84 +1,14 @@
const db = require('../../data/db');
const db = require('../../data/dbConfig');
module.exports = {
addAccount,
getAccountById,
updateAccount,
deleteAccount,
getMeetingsByAccountId,
getAccountByEmail,
addUser,
};
function addAccount(data) {
return db('account')
.insert(data)
.returning([
'id',
'username',
'email',
'timezone',
'earliest_time',
'latest_time',
]);
}
function updateAccount(data, id) {
return db('account')
.where({ id })
.update(data)
.returning([
'id',
'username',
'email',
'timezone',
'earliest_time',
'latest_time',
]);
}
function deleteAccount(id) {
return db('account').where({ id }).del();
}
function getMeetingsByAccountId(account_id) {
return db('participant as p')
.select(
'm.id',
'm.title',
'm.description',
'm.start_time',
'm.duration',
'm.status',
)
.join('meeting as m', { 'm.id': 'p.meeting_id' })
.where({ account_id });
}
function getAccountById(id) {
return db('account')
.where({ id })
.first()
.select(
'id',
'username',
'email',
'timezone',
'earliest_time',
'latest_time',
);
}
function getAccountByEmail(email) {
return db('account')
.where({ email })
.first()
.select(
'id',
'username',
'password',
'email',
'timezone',
'earliest_time',
'latest_time',
);
function addUser(userData) {
// TODO Complete query without providing id
// right now if ID is not provided, pg-promise send an erro fo mising column
return db.one(
'INSERT INTO account VALUES(emptyUpdate, ${username}, ${email}, ${password}, ${timezone}, ${earliest_time}, ${latest_time}) RETURNING *',
userData,
);
}

View File

@ -1,23 +0,0 @@
const db = require('../../data/db');
module.exports = {
addAvailability,
deleteAvailability,
};
function addAvailability(data) {
return db('availability')
.insert(data)
.returning([
'id',
'participant_id',
'possible_date_id',
'preference',
'start_time',
'end_time',
]);
}
function deleteAvailability(id) {
return db('availability').where({ id }).del();
}

View File

@ -1,93 +0,0 @@
const db = require('../../data/db');
module.exports = {
addMeeting,
getMeetingById,
updateMeeting,
deleteMeeting,
getParticipantsByMeetingId,
getPossibleDatesByMeetingId,
getAvailabilityByMeetingId,
};
function addMeeting(data) {
return db('meeting')
.insert(data)
.returning([
'id',
'title',
'description',
'start_time',
'duration',
'status',
]);
}
function updateMeeting(data, id) {
return db('meeting')
.where({ id })
.update(data)
.returning([
'id',
'title',
'description',
'start_time',
'duration',
'status',
]);
}
function deleteMeeting(id) {
return db('meeting').where({ id }).del();
}
function getMeetingById(id) {
return db('meeting')
.where({ id })
.first()
.select(
'id',
'title',
'description',
'start_time',
'duration',
'status',
);
}
function getParticipantsByMeetingId(meeting_id) {
return db('participant as p')
.select(
'p.id',
'p.email',
'p.account_id',
'p.meeting_id',
'p.quorum',
'p.mandatory',
'p.host',
'p.answered',
)
.join('meeting as m', { 'm.id': 'p.meeting_id' })
.where({ meeting_id });
}
function getPossibleDatesByMeetingId(meeting_id) {
return db('possible_date as p')
.select('p.id', 'p.meeting_id', 'p.possible_date')
.join('meeting as m', { 'm.id': 'p.meeting_id' })
.where({ meeting_id });
}
function getAvailabilityByMeetingId(meeting_id) {
return db('availability as a')
.select(
'a.id',
'a.participant_id',
'a.possible_date_id',
'a.preference',
'a.start_time',
'a.end_time',
)
.join('meeting as m', { 'm.id': 'a.meeting_id' })
.where({ meeting_id });
}

View File

@ -1,42 +0,0 @@
const db = require('../../data/db');
module.exports = {
addParticipant,
updateParticipant,
deleteParticipant,
};
function addParticipant(data) {
return db('participant')
.insert(data)
.returning([
'id',
'email',
'account_id',
'meeting_id',
'quorum',
'mandatory',
'host',
'answered',
]);
}
function updateParticipant(data, id) {
return db('participant')
.where({ id })
.update(data)
.returning([
'id',
'email',
'account_id',
'meeting_id',
'quorum',
'mandatory',
'host',
'answered',
]);
}
function deleteParticipant(id) {
return db('participant').where({ id }).del();
}

View File

@ -1,16 +0,0 @@
const db = require('../../data/db');
module.exports = {
addPossibleDate,
deletePossibleDate,
};
function addPossibleDate(data) {
return db('possible_date')
.insert(data)
.returning(['id', 'meeting_id', 'possible_date']);
}
function deletePossibleDate(id) {
return db('possible_date').where({ id }).del();
}

View File

@ -1,105 +1,17 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
let express = require('express');
let router = express.Router();
const { saltingRounds } = require('../../config/config');
const { authenticate } = require('../../middlewares/authenticate');
const { validateAccountID } = require('../../middlewares/validateAccountID');
let Account = require('../models/accountModel');
const Account = require('../models/accountModel');
// TODO : remove if unused
// Add a user
router.post('/', async (req, res) => {
const data = { ...req.body };
const hash = bcrypt.hashSync(data.password, saltingRounds);
data.password = hash;
const userData = { ...req.body };
try {
const [account] = await Account.addAccount(data);
res.status(201).json(account);
const user = await Account.addUser(userData);
res.status(201).json(user);
} catch (error) {
res.status(500).json({ message: 'Failed to add new account.', error });
}
});
router.put('/:id', authenticate, validateAccountID, async (req, res) => {
const data = { ...req.body };
const { id } = req.params;
if (data.password) {
const hash = bcrypt.hashSync(data.password, 10);
data.password = hash;
}
try {
const account = await Account.updateAccount(data, id);
res.status(200).json(...account);
} catch (error) {
res.status(500).json({
message: `Failed to update account with id ${id}.`,
error,
});
}
});
router.delete('/:id', authenticate, validateAccountID, async (req, res) => {
const { id } = req.params;
try {
const account = await Account.deleteAccount(id);
res.status(200).json({
message: `Account with id ${id} successfully deleted.`,
});
} catch (error) {
res.status(500).json({
message: `Failed to delete account with id ${id}.`,
error,
});
}
});
router.get(
'/:id/meetings',
authenticate,
validateAccountID,
async (req, res) => {
const { id } = req.params;
try {
const meetings = await Account.getMeetingsByAccountId(id);
if (meetings.length == 0) {
res.status(200).json({
message: `There are no meetings for account with id ${id}.`,
});
} else {
res.status(200).json(meetings);
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch meetings with account id ${id}.`,
error,
});
}
},
);
router.get('/:id', authenticate, async (req, res) => {
const { id } = req.params;
try {
const account = await Account.getAccountById(id);
if (typeof account == 'undefined') {
res.status(404).json({
message: `Account with id ${id} doesn't exist.`,
});
} else {
res.status(200).json(account);
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch account with id ${id}.`,
error,
});
res.status(500).json({ message: 'Failed to add new user', error });
}
});

View File

@ -1,48 +0,0 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const Account = require('../models/accountModel');
const { saltingRounds } = require('../../config/config');
const { generateToken } = require('../../helpers/generateToken');
router.post('/register', async (req, res) => {
const data = req.body;
data.password = bcrypt.hashSync(data.password, saltingRounds);
try {
const [user] = await Account.addAccount(data);
const token = generateToken(user);
res.status(201).json({ user, token });
} catch (error) {
res.status(500).json({
message: `Failed to add new account.`,
error,
});
}
});
router.post('/login', async (req, res) => {
let { email, password } = req.body;
try {
const user = await Account.getAccountByEmail(email);
if (bcrypt.compareSync(password, user.password)) {
const token = generateToken(user);
delete user.password;
res.status(200).json({
user,
token,
});
} else {
throw new Error();
}
} catch (error) {
res.status(401).json({
message: `Invalid credentials`,
});
}
});
module.exports = router;

View File

@ -1,37 +0,0 @@
const express = require('express');
const router = express.Router();
const { authenticate } = require('../../middlewares/authenticate');
const Availability = require('../models/availabilityModel');
router.post('/', authenticate, async (req, res) => {
const data = { ...req.body };
try {
const [availability] = await Availability.addAvailability(data);
res.status(201).json(availability);
} catch (error) {
res.status(500).json({
message: 'Failed to add a new availability.',
error,
});
}
});
router.delete('/:id', authenticate, async (req, res) => {
const { id } = req.params;
try {
const availability = await Availability.deleteAvailability(id);
res.status(200).json({
message: `Availability with id ${id} successfully deleted.`,
});
} catch (error) {
res.status(500).json({
message: `Failed to delete availability with id ${id}.`,
error,
});
}
});
module.exports = router;

View File

@ -1,159 +0,0 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const { v4: uuidv4 } = require('uuid');
const { authenticate } = require('../../middlewares/authenticate');
const { validateMeetingID } = require('../../middlewares/validateMeetingID');
const Meeting = require('../models/meetingModel');
router.post('/', authenticate, async (req, res) => {
id = uuidv4();
data = { ...req.body, id };
if (data.password) {
const hash = bcrypt.hashSync(data.password, 14);
data.password = hash;
}
try {
const [meeting] = await Meeting.addMeeting(data);
res.status(201).json(meeting);
} catch (error) {
res.status(500).json({ message: 'Failed to add new meeting.', error });
}
});
router.put('/:id', authenticate, validateMeetingID, async (req, res) => {
const data = { ...req.body };
const { id } = req.params;
if (data.password) {
const hash = bcrypt.hashSync(data.password, 14);
data.password = hash;
}
try {
const meeting = await Meeting.updateMeeting(data, id);
res.status(200).json(meeting);
} catch (error) {
res.status(500).json({
message: `Failed to update meeting with id ${id}.`,
error,
});
}
});
router.delete('/:id', authenticate, validateMeetingID, async (req, res) => {
const { id } = req.params;
try {
const meeting = await Meeting.deleteMeeting(id);
res.status(200).json({
message: `Meeting with id ${id} successfully deleted.`,
});
} catch (error) {
res.status(500).json({
message: `Failed to delete meeting with id ${id}.`,
error,
});
}
});
router.get('/:id', authenticate, async (req, res) => {
const { id } = req.params;
try {
const meeting = await Meeting.getMeetingById(id);
if (typeof meeting == 'undefined') {
res.status(404).json({
message: `Meeting with id ${id} could not be found.`,
});
} else {
res.status(200).json(meeting);
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch meeting with id ${id}`,
error,
});
}
});
router.get(
'/:id/participants',
authenticate,
validateMeetingID,
async (req, res) => {
const { id } = req.params;
try {
const participants = await Meeting.getParticipantsByMeetingId(id);
if (participants.length == 0) {
res.status(200).json({
message: `There are no participants for meeting with id ${id}.`,
});
} else {
res.status(200).json(participants);
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch participants for meeting with id ${id}.`,
error,
});
}
},
);
router.get(
'/:id/possible-dates',
authenticate,
validateMeetingID,
async (req, res) => {
const { id } = req.params;
try {
const possibleDates = await Meeting.getPossibleDatesByMeetingId(id);
if (possibleDates.length == 0) {
res.status(200).json({
message: `There are no possibles dates for meeting with id ${id}.`,
});
} else {
res.status(200).json(possibleDates);
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch possible dates for meeting with id ${id}`,
error,
});
}
},
);
router.get(
'/:id/availability',
authenticate,
validateMeetingID,
async (req, res) => {
const { id } = req.params;
try {
const availability = await Meeting.getAvailabilityByMeetingId(id);
if (availability.length == 0) {
res.status(200).json({
message: `There are no possibles dates for meeting with id ${id}.`,
});
} else {
res.status(200).json(availability);
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch availability for meeting with id ${id}`,
error,
});
}
},
);
module.exports = router;

View File

@ -1,115 +0,0 @@
const express = require('express');
const router = express.Router();
const { v4: uuidv4 } = require('uuid');
const { authenticate } = require('../../middlewares/authenticate');
const {
validateParticipantID,
} = require('../../middlewares/validateParticipantID');
const Participant = require('../models/participantModel');
const { createInvite } = require('../../services/email/emailModels');
const sendEmail = require('../../services/email/sendEmail');
router.post('/', authenticate, async (req, res) => {
id = uuidv4();
let data = { ...req.body, id };
const {
account_id,
email,
meeting_id,
quorum,
mandatory,
host,
answered,
meetingTitle,
senderUsername,
} = data;
// Create invite message
const invite = createInvite({
receiver: email,
senderUsername,
meetingTitle,
meetingId: meeting_id,
});
const participantData = {
id,
account_id,
email,
meeting_id,
quorum,
mandatory,
host,
answered,
};
try {
const [participant] = await Participant.addParticipant(participantData);
// Send email
sendEmail(invite);
res.status(201).json(participant);
} catch (error) {
console.log(error);
res.status(500).json({
message: 'Failed to add new participant.',
error,
});
}
});
router.put('/:id', authenticate, validateParticipantID, async (req, res) => {
const data = { ...req.body };
const { id } = req.params;
try {
const participant = await Participant.updateParticipant(data, id);
res.status(200).json(participant);
} catch (error) {
res.status(500).json({
message: 'Failed to update participant.',
error,
});
}
});
router.delete('/:id', authenticate, validateParticipantID, async (req, res) => {
const { id } = req.params;
try {
const participant = await Participant.deleteParticipant(id);
res.status(200).json({
message: `Participant with id ${id} successfully deleted.`,
});
} catch (error) {
res.status(500).json({
message: `Failed to delete participant with id ${id}.`,
error,
});
}
});
router.get('/:id', authenticate, async (req, res) => {
const { id } = req.params;
try {
const participant = await Participant.getParticipantById(id);
if (typeof participant == 'undefined') {
res.status(404).json({
message: `Participant with id ${id} doesn't exist.`,
});
} else {
res.status(200).json(participant);
}
} catch (error) {
res.status(500).json({
message: `Failed to get participant with id ${id}.`,
error,
});
}
});
module.exports = router;

View File

@ -1,37 +0,0 @@
const express = require('express');
const router = express.Router();
const { authenticate } = require('../../middlewares/authenticate');
const PossibleDate = require('../models/possibleDateModel');
router.post('/', authenticate, async (req, res) => {
const data = { ...req.body };
try {
const [possibleDate] = await PossibleDate.addPossibleDate(data);
res.status(201).json(possibleDate);
} catch (error) {
res.status(500).json({
message: 'Failed to add a new possible date.',
error,
});
}
});
router.delete('/:id', authenticate, async (req, res) => {
const { id } = req.params;
try {
const possibleDate = await PossibleDate.deletePossibleDate(id);
res.status(200).json({
message: `Possible date with id ${id} successfully deleted.`,
});
} catch (error) {
res.status(500).json({
message: `Failed to delete possible date with id ${id}.`,
error,
});
}
});
module.exports = router;

View File

@ -1,50 +1,13 @@
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const helmet = require('helmet');
const logSmtpStatus = require('../services/email/logSmtpStatus');
const express = require("express");
const accountsRoute = require('./routes/accountRoute');
const meetingsRoute = require('./routes/meetingRoute');
const participantsRoute = require('./routes/participantRoute');
const possibleDatesRoute = require('./routes/possibleDateRoute');
const availabilityRoute = require('./routes/availabilityRoute');
const authRoute = require('./routes/authRoute');
const server = express();
server.use(cors());
server.use(express.json());
server.use(helmet());
server.use(bodyParser.json());
server.use(
bodyParser.urlencoded({
extended: true,
}),
);
// TODO: Add validation to all requests
// server.use(expressValidator());
server.use('/api/accounts', accountsRoute);
server.use('/api/meetings', meetingsRoute);
server.use('/api/participants', participantsRoute);
server.use('/api/possible-dates', possibleDatesRoute);
server.use('/api/availability', availabilityRoute);
server.use('/api/auth', authRoute);
// Uncomment to log SMTP status. If config is correct,
// the console will output:'SMTP is correctly configured.'
// logSmtpStatus();
server.get('/', (req, res) => {
res.json({
message: `Meeting Planner Backend API :)`,
documentation: `Check out: https://git.ruihildt.xyz/meeting-planner/backend`,
});
});
server.get('/', (req, res) =>
res.status(200).send('<h2>Welcome to Meeting Planner Backend API service.</h2>'),
);
module.exports = server;

View File

@ -1,27 +0,0 @@
// from '.env' in root folder
module.exports = {
// APP
port: process.env.PORT || 3001,
environment: process.env.NODE_ENV || 'development',
// DATABASE
dbURL: process.env.DATABASE_URL,
// JWT
jwtSecret: process.env.JWT_SECRET,
saltingRounds: 10,
// NODEMAILER
smtpHost: process.env.SMTP_HOST,
smtpPort: process.env.SMTP_PORT,
smtpUsername: process.env.SMTP_USERNAME,
smtpPassword: process.env.SMTP_PASSWORD,
smtpRequireTLS: true, // True to force use of StartTLS
smtpSecure: false, // False to force use of StartTLS
smtpPool: true, // Pool connections to server
// EMAIL
appURL: 'https://meetingplanner.com',
sender: 'no-reply@ruihildt.xyz',
signature: 'The Meeting Planner Team',
};

View File

@ -1,8 +0,0 @@
const knex = require('knex');
const knexfile = require('../knexfile');
const { environment} = require('../config/config')
const env = environment || 'development';
const configOptions = knexfile[env];
module.exports = knex(configOptions);

5
data/dbConfig.js Normal file
View File

@ -0,0 +1,5 @@
const pgp = require('pg-promise')();
const db = pgp(process.env.DATABASE_URL);
module.exports = db;

View File

@ -0,0 +1,5 @@
## How to do a migration
- Create `.env` to the root folder
- Add the database url to the `.env` file:
`DATABASE_URL=postgres://username:password@host:port/database`
- Run `npm run migrate up`

View File

@ -1,16 +0,0 @@
exports.up = (knex) => {
return knex.schema.createTable('account', (table) => {
table.increments('id').primary();
table.string('username').notNullable();
table.string('email').notNullable().unique();
table.string('password').notNullable();
table.string('timezone');
table.string('earliest_time');
table.string('latest_time');
table.timestamps(true, true);
});
};
exports.down = (knex) => {
return knex.schema.dropTable('account');
};

View File

@ -1,16 +0,0 @@
exports.up = (knex) => {
return knex.schema.createTable('meeting', (table) => {
table.uuid('id').primary();
table.string('title').notNullable();
table.string('description');
table.datetime('start_time');
table.integer('duration').notNullable();
table.boolean('status').notNullable();
table.string('password');
table.timestamps(true, true);
});
};
exports.down = (knex) => {
return knex.schema.dropTable('meeting');
};

View File

@ -1,17 +0,0 @@
exports.up = (knex) => {
return knex.schema.createTable('participant', (table) => {
table.uuid('id').primary();
table.integer('account_id').unsigned();
table.string('email');
table.uuid('meeting_id').unsigned().notNullable();
table.boolean('quorum');
table.boolean('mandatory');
table.boolean('host').notNullable();
table.boolean('answered').notNullable();
table.timestamps(true, true);
});
};
exports.down = (knex) => {
return knex.schema.dropTable('participant');
};

View File

@ -1,16 +0,0 @@
exports.up = (knex) => {
return knex.schema.createTable('possible_date', (table) => {
table.increments('id').primary();
table.uuid('meeting_id').unsigned().notNullable();
table
.foreign('meeting_id')
.references('meeting.id')
.onDelete('cascade');
table.string('possible_date').notNullable();
table.timestamps(true, true);
});
};
exports.down = (knex) => {
return knex.schema.dropTable('possible_date');
};

View File

@ -1,23 +0,0 @@
exports.up = (knex) => {
return knex.schema.createTable('availability', (table) => {
table.increments('id').primary();
table.uuid('participant_id').notNullable();
table
.foreign('participant_id')
.references('participant.id')
.onDelete('cascade');
table.integer('possible_date_id').notNullable();
table
.foreign('possible_date_id')
.references('possible_date.id')
.onDelete('cascade');
table.boolean('preference').notNullable();
table.datetime('start_time').notNullable();
table.datetime('end_time').notNullable();
table.timestamps(true, true);
});
};
exports.down = (knex) => {
return knex.schema.dropTable('availability');
};

View File

@ -1,61 +0,0 @@
const bcrypt = require('bcryptjs');
const { saltingRounds } = require('../../config/config');
exports.seed = function (knex) {
// Deletes ALL existing entries
return knex('account')
.del()
.then(function () {
const accountSeeds = [
{
username: 'liza',
email: 'liza@example.com',
password: 'password',
timezone: 'Europe/Brussels',
earliest_time: '00:00',
latest_time: '24:00',
},
{
username: 'emile',
email: 'emile@example.com',
password: 'password',
timezone: 'America/New_York',
earliest_time: '09:00',
latest_time: '20:00',
},
{
username: 'jack',
email: 'jack@example.com',
password: 'password',
timezone: 'Asia/Kolkata',
earliest_time: '10:30',
latest_time: '17:00',
},
{
username: 'cynthia',
email: 'cynthia@example.com',
password: 'password',
timezone: 'Europe/Brussels',
earliest_time: '06:30',
latest_time: '12:00',
},
{
username: 'celine',
email: 'celine@example.com',
password: 'password',
timezone: 'Europe/Brussels',
earliest_time: '10:30',
latest_time: '20:00',
},
];
// Hash password for each user
accountSeeds.forEach((account) => {
let hash = bcrypt.hashSync(account.password, saltingRounds);
account.password = hash;
});
// Inserts seed entries
return knex('account').insert(accountSeeds);
});
};

View File

@ -1,70 +0,0 @@
exports.seed = function (knex) {
// Deletes ALL existing entries
return knex('meeting')
.del()
.then(function () {
// Inserts seed entries
return knex('meeting').insert([
{
id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
title: 'Worldwide meeting I',
description:
"Let's find the best ethical growth hacking technics together. Yeah, fun.",
start_time: '2025-02-19T15:00:00Z',
duration: 90,
status: 0,
password: 'simple-meeting-password',
},
{
id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
title: 'Worldwide meeting II',
description:
"Let's find the best ethical growth hacking technics together. Yeah, fun.",
start_time: '2025-05-18T15:00:00Z',
duration: 90,
status: 0,
password: 'simple-meeting-password',
},
{
id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
title: 'Worldwide meeting III',
description:
"Let's find the best ethical growth hacking technics together. Yeah, fun.",
start_time: '2025-10-18T15:00:00Z',
duration: 90,
status: 0,
password: 'simple-meeting-password',
},
{
id: 'aa639e05-be03-4202-a18e-aae3bc5153f0',
title: 'Worldwide meeting IV',
description:
"Let's find the best ethical growth hacking technics together. Yeah, fun.",
start_time: '2025-06-16T15:00:00Z',
duration: 90,
status: 0,
password: 'simple-meeting-password',
},
{
id: 'a4a9b71a-91af-4e35-8377-96f56fa7f6b8',
title: 'Worldwide meeting V',
description:
"Let's find the best ethical growth hacking technics together. Yeah, fun.",
start_time: '2025-04-27T15:00:00Z',
duration: 90,
status: 0,
password: 'simple-meeting-password',
},
{
id: '741f300a-a137-499d-b725-73770ac6fe70',
title: 'Worldwide meeting VI',
description:
"Let's find the best ethical growth hacking technics together. Yeah, fun.",
start_time: ' 2025-08-21T15:00:00Z',
duration: 90,
status: 0,
password: 'simple-meeting-password',
},
]);
});
};

View File

@ -1,93 +0,0 @@
exports.seed = function (knex) {
// Deletes ALL existing entries
return knex('possible_date')
.del()
.then(function () {
// Inserts seed entries
return knex('possible_date').insert([
// Meeting 1
{
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date: '2025-02-18',
},
{
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date: '2025-02-19',
},
{
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date: '2025-02-20',
},
{
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date: '2025-02-21',
},
// Meeting 2
{
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date: '2025-05-18',
},
{
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date: '2025-05-19',
},
{
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date: '2025-05-22',
},
// Meeting 3
{
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date: '2025-08-18',
},
{
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date: '2025-10-18',
},
{
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date: '2025-12-18',
},
// Meeting 4
{
meeting_id: 'aa639e05-be03-4202-a18e-aae3bc5153f0',
possible_date: '2025-06-18',
},
{
meeting_id: 'aa639e05-be03-4202-a18e-aae3bc5153f0',
possible_date: '2025-06-16',
},
// Meeting 5
{
meeting_id: 'a4a9b71a-91af-4e35-8377-96f56fa7f6b8',
possible_date: '2025-04-06',
},
{
meeting_id: 'a4a9b71a-91af-4e35-8377-96f56fa7f6b8',
possible_date: '2025-04-12',
},
{
meeting_id: 'a4a9b71a-91af-4e35-8377-96f56fa7f6b8',
possible_date: '2025-04-27',
},
// Meeting 6
{
meeting_id: '741f300a-a137-499d-b725-73770ac6fe70',
possible_date: '2025-06-16',
},
{
meeting_id: '741f300a-a137-499d-b725-73770ac6fe70',
possible_date: '2025-07-19',
},
{
meeting_id: '741f300a-a137-499d-b725-73770ac6fe70',
possible_date: '2025-08-21',
},
]);
});
};

View File

@ -1,118 +0,0 @@
exports.seed = function (knex) {
// Deletes ALL existing entries
return knex('participant')
.del()
.then(function () {
// Inserts seed entries
return knex('participant').insert([
{
id: '65fda742-e91e-11ea-adc1-0242ac120002',
account_id: 1,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fda9ae-e91e-11ea-adc1-0242ac120002',
account_id: 1,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdaaa8-e91e-11ea-adc1-0242ac120002',
account_id: 1,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdab84-e91e-11ea-adc1-0242ac120002',
account_id: 2,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdac56-e91e-11ea-adc1-0242ac120002',
account_id: 3,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdad1e-e91e-11ea-adc1-0242ac120002',
account_id: 4,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdb066-e91e-11ea-adc1-0242ac120002',
account_id: 4,
meeting_id: 'aa639e05-be03-4202-a18e-aae3bc5153f0',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdade6-e91e-11ea-adc1-0242ac120002',
account_id: 4,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdb12e-e91e-11ea-adc1-0242ac120002',
account_id: 5,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdb1f6-e91e-11ea-adc1-0242ac120002',
account_id: 5,
meeting_id: 'a4a9b71a-91af-4e35-8377-96f56fa7f6b8',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdb2b4-e91e-11ea-adc1-0242ac120002',
account_id: 5,
meeting_id: '741f300a-a137-499d-b725-73770ac6fe70',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
{
id: '65fdb372-e91e-11ea-adc1-0242ac120002',
account_id: 5,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
quorum: 0,
mandatory: 0,
host: 0,
answered: 0,
},
]);
});
};

View File

@ -1,252 +0,0 @@
exports.seed = function (knex) {
// Deletes ALL existing entries
return knex('availability')
.del()
.then(function () {
// Inserts seed entries
return knex('availability').insert([
// Meeting 1 - Day 1
{
account_id: 1,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date_id: 1,
preference: 0,
start_time: '2025-02-18T09:00:00Z',
end_time: '2025-02-18T22:00:00Z',
},
{
account_id: 2,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date_id: 1,
preference: 0,
start_time: '2025-02-18T10:00:00Z',
end_time: '2025-02-18T20:00:00Z',
},
// Meeting 1 - Day 2
{
account_id: 1,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date_id: 2,
preference: 0,
start_time: '2025-02-19T10:00:00Z',
end_time: '2025-02-19T14:00:00Z',
},
{
account_id: 2,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date_id: 2,
preference: 0,
start_time: '2025-02-19T09:00:00Z',
end_time: '2025-02-19T13:00:00Z',
},
// Meeting 1 - Day 3
{
account_id: 1,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date_id: 3,
preference: 0,
start_time: '2025-02-20T15:00:00Z',
end_time: '2025-02-20T18:00:00Z',
},
{
account_id: 2,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date_id: 3,
preference: 0,
start_time: '2025-02-20T14:00:00Z',
end_time: '2025-02-20T20:00:00Z',
},
// Meeting 1 - Day 4
{
account_id: 1,
meeting_id: '03ac7a10-316f-46e8-bb55-8611e7e5b31c',
possible_date_id: 4,
preference: 0,
start_time: '2025-02-21T00:00:00Z',
end_time: '2025-02-21T22:00:00Z',
},
// Meeting 2 - Day 1
{
account_id: 1,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 5,
preference: 0,
start_time: '2025-05-18T09:00:00Z',
end_time: '2025-05-18T17:00:00Z',
},
{
account_id: 3,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 5,
preference: 0,
start_time: '2025-05-18T15:00:00Z',
end_time: '2025-05-18T22:00:00Z',
},
{
account_id: 4,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 5,
preference: 0,
start_time: '2025-05-18T06:00:00Z',
end_time: '2025-05-18T17:00:00Z',
},
{
account_id: 5,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 5,
preference: 0,
start_time: '2025-05-18T06:00:00Z',
end_time: '2025-05-18T22:00:00Z',
},
{
account_id: 5,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 5,
preference: 0,
start_time: '2025-05-18T15:00:00Z',
end_time: '2025-05-18T20:00:00Z',
},
// Meeting 2 - Day 2
{
account_id: 1,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 6,
preference: 0,
start_time: '2025-05-19T15:00:00Z',
end_time: '2025-05-19T21:00:00Z',
},
{
account_id: 3,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 6,
preference: 0,
start_time: '2025-05-19T13:00:00Z',
end_time: '2025-05-19T19:00:00Z',
},
{
account_id: 4,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 6,
preference: 0,
start_time: '2025-05-19T16:00:00Z',
end_time: '2025-05-19T22:00:00Z',
},
{
account_id: 5,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 6,
preference: 0,
start_time: '2025-05-19T10:00:00Z',
end_time: '2025-05-19T15:00:00Z',
},
// Meeting 2 - Day 3
{
account_id: 1,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 7,
preference: 0,
start_time: '2025-05-22T09:00:00Z',
end_time: '2025-05-22T13:00:00Z',
},
{
account_id: 3,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 7,
preference: 0,
start_time: '2025-05-22T15:00:00Z',
end_time: '2025-05-22T19:00:00Z',
},
{
account_id: 4,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 7,
preference: 0,
start_time: '2025-05-22T17:00:00Z',
end_time: '2025-05-22T12:00:00Z',
},
{
account_id: 5,
meeting_id: '2e8f3748-ea5a-4d20-b9a8-683ac65f5634',
possible_date_id: 7,
preference: 0,
start_time: '2025-05-22T20:00:00Z',
end_time: '2025-05-22T23:00:00Z',
},
// Meeting 3 - Day 1
{
account_id: 1,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 8,
preference: 0,
start_time: '2025-08-18T15:00:00Z',
end_time: '2025-08-18T21:00:00Z',
},
{
account_id: 5,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 8,
preference: 0,
start_time: '2025-08-18T09:00:00Z',
end_time: '2025-08-18T13:00:00Z',
},
// Meeting 3 - Day 2
{
account_id: 1,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 9,
preference: 0,
start_time: '2025-10-18T15:00:00Z',
end_time: '2025-10-18T18:00:00Z',
},
{
account_id: 4,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 9,
preference: 0,
start_time: '2025-10-18T09:00:00Z',
end_time: '2025-10-18T14:00:00Z',
},
{
account_id: 5,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 9,
preference: 0,
start_time: '2025-10-18T06:00:00Z',
end_time: '2025-10-18T10:00:00Z',
},
{
account_id: 5,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 9,
preference: 0,
start_time: '2025-10-18T14:00:00Z',
end_time: '2025-10-18T18:00:00Z',
},
// Meeting 3 - Day 3
{
account_id: 1,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 10,
preference: 0,
start_time: '2025-12-18T06:00:00Z',
end_time: '2025-12-18T15:00:00Z',
},
{
account_id: 4,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 10,
preference: 0,
start_time: '2025-12-18T10:00:00Z',
end_time: '2025-12-18T18:00:00Z',
},
{
account_id: 5,
meeting_id: 'a8344a68-7961-4bff-bb3b-b288f3abcf1c',
possible_date_id: 10,
preference: 0,
start_time: '2025-12-18T09:00:00Z',
end_time: '2025-12-18T20:00:00Z',
},
]);
});
};

View File

@ -1,17 +0,0 @@
const jwt = require('jsonwebtoken');
const { jwtSecret } = require('../config/config');
function generateToken(user) {
const payload = {
username: user.username,
email: user.email,
};
const options = {
expiresIn: '30d',
};
return jwt.sign(payload, jwtSecret, options);
}
exports.generateToken = generateToken;

View File

@ -1,8 +1,7 @@
const server = require('./api/server.js');
const { port } = require('./config/config');
require('dotenv').config()
const app = require("./api/server");
const PORT = process.env.PORT || 3000;
server.listen(port, () =>
console.log(
`Meeting Planner Backend listening at http://localhost:${port}`,
),
app.listen(PORT, () =>
console.log(`Meeting Planner Backend listening at http://localhost:${PORT}`),
);

View File

@ -1,34 +0,0 @@
require('dotenv').config();
const { dbURL } = require('./config/config');
module.exports = {
development: {
client: 'pg',
connection: dbURL,
migrations: {
directory: './data/migrations',
},
seeds: { directory: './data/seeds' },
useNullAsDefault: true,
},
testing: {
client: 'pg',
connection: dbURL,
migrations: {
directory: './data/migrations',
},
seeds: { directory: './data/seeds' },
useNullAsDefault: true,
},
production: {
client: 'pg',
connection: dbURL,
migrations: {
directory: './data/migrations',
},
seeds: { directory: './data/seeds' },
useNullAsDefault: true,
},
};

View File

@ -1,20 +0,0 @@
const jwt = require('jsonwebtoken');
const { jwtSecret } = require('../config/config');
function authenticate(req, res, next) {
const token = req.get('Authorization');
if (token) {
jwt.verify(token, jwtSecret, (err, decoded) => {
if (err) return res.status(401).json(err);
req.decoded = decoded;
next();
});
} else {
return res.status(401).json({
error: 'No token provided, must be set on the Authorization Header',
});
}
}
exports.authenticate = authenticate;

View File

@ -1,23 +0,0 @@
const Account = require('../api/models/accountModel');
async function validateAccountID(req, res, next) {
const { id } = req.params;
try {
const account = await Account.getAccountById(id);
if (typeof account == 'undefined') {
return res.status(404).json({
message: `Account with id ${id} doesn't exist.`,
});
} else {
next();
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch account with id ${id}.`,
error,
});
}
}
exports.validateAccountID = validateAccountID;

View File

@ -1,23 +0,0 @@
const Meeting = require('../api/models/meetingModel');
async function validateMeetingID(req, res, next) {
const { id } = req.params;
try {
const meeting = await Meeting.getMeetingById(id);
if (typeof meeting == 'undefined') {
return res.status(404).json({
message: `Meeting with id ${id} doesn't exist.`,
});
} else {
next();
}
} catch (error) {
res.status(500).json({
message: `Failed to fetch meeting with id ${id}.`,
error,
});
}
}
exports.validateMeetingID = validateMeetingID;

View File

@ -1,23 +0,0 @@
const Participant = require('../api/models/participantModel');
async function validateParticipantID(req, res, next) {
const { id } = req.params;
try {
const participant = await Participant.getParticipantById(id);
if (typeof participant == 'undefined') {
res.status(404).json({
message: `Participant with id ${id} doesn't exist.`,
});
} else {
next();
}
} catch (error) {
res.status(500).json({
message: `Failed to get participant with id ${id}`,
error,
});
}
}
exports.validateParticipantID = validateParticipantID;

View File

@ -0,0 +1,97 @@
const { PgLiteral } = require('node-pg-migrate');
exports.shorthands = {
id: { type: 'serial', primaryKey: true },
varchar: { type: 'varchar(128)' },
varchar_req: { type: 'varchar(128)', notNull: true },
created_at: {
type: 'timestamp',
notNull: true,
default: PgLiteral.create('CURRENT_TIMESTAMP'),
},
};
exports.up = (pgm) => {
pgm.createTable('account', {
id: 'id',
username: 'varchar_req',
email: 'varchar_req',
password: 'varchar_req',
timezone: 'varchar',
earliest_time: 'time',
latest_time: 'time',
createdAt: 'created_at',
});
pgm.createTable('meeting', {
id: { type: 'uuid', primaryKey: true },
title: 'varchar_req',
description: 'varchar',
start_time: 'time',
timezone: 'varchar',
duration: 'int',
status: 'boolean',
password: 'varchar',
createdAt: 'created_at',
});
pgm.createTable('possible_date', {
id: 'id',
meeting_id: {
type: 'uuid',
references: 'meeting(id)',
notNull: true,
onDelete: 'cascade',
},
possible_date: { type: 'date', notNull: true },
});
pgm.createTable('participant', {
id: 'id',
account_id: {
type: 'int',
references: 'account(id)',
notNull: true,
onDelete: 'cascade',
},
meeting_id: {
type: 'uuid',
references: 'meeting(id)',
notNull: true,
onDelete: 'cascade',
},
earliest_time: 'time',
latest_time: 'time',
quorum: 'boolean',
mandatory: 'boolean',
host: 'boolean',
answered: 'boolean',
timezone: 'varchar',
createdAt: 'created_at',
});
pgm.createTable('availibility', {
id: 'id',
participant_id: {
type: 'id',
references: 'participant(id)',
notNull: true,
onDelete: 'cascade',
},
possible_date_id: {
type: 'id',
references: 'possible_date(id)',
notNull: true,
onDelete: 'cascade',
},
preference: { type: 'boolean', notNull: true },
start_time: { type: 'timestamp', notNull: true },
end_time: { type: 'timestamp', notNull: true },
timezone: 'varchar_req',
createdAt: 'created_at',
});
};
// exports.down = (pgm) => {
// pgm.dropTable('accounts', { ifExists: true });
// };

3210
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,24 @@
{
"name": "backend",
"version": "1.0.0",
"description": "Backend for Meeting Planner",
"main": "index.js",
"scripts": {
"dev": "nodemon index.js",
"start": "node index.js"
},
"repository": {
"type": "git",
"url": "ssh://git@git.armada.digital:29418/meeting-planner/backend.git"
},
"author": "rui hildt",
"license": "AGPL-3.0-or-later",
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-validator": "^6.4.1",
"helmet": "^3.22.0",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.1",
"nodemailer": "^6.4.11",
"pg": "^8.0.3",
"pgtools": "^0.3.0",
"uuid": "^8.0.0"
}
"name": "backend",
"version": "1.0.0",
"description": "Backend for Meeting Planner",
"main": "index.js",
"scripts": {
"dev": "nodemon index.js",
"prod": "node index.js",
"migrate": "node-pg-migrate"
},
"repository": {
"type": "git",
"url": "ssh://git@git.armada.digital:29418/meeting-planner/backend.git"
},
"author": "rui hildt",
"license": "AGPL-3.0-or-later",
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.17.1",
"node-pg-migrate": "^4.7.0",
"pg": "^8.0.3",
"pg-promise": "^10.5.2"
}
}

View File

@ -1,26 +0,0 @@
const { sender, signature, appURL } = require('../../config/config');
module.exports = {
createInvite,
};
function createInvite({ receiver, senderUsername, meetingTitle, meetingId }) {
const invite = {
from: sender,
to: receiver,
subject: `Invitation | ${meetingTitle}`,
text: `Hi,
${senderUsername} invites you to participate to the poll '${meetingTitle}'.
To participate, please fill in you availability: ${appURL}/meetings/${meetingId}
Have a beautiful day,
${signature} | ${appURL}
`,
};
return invite;
}

View File

@ -1,14 +0,0 @@
const transporter = require('./transporter');
function logSmtpStatus() {
transporter.verify(function (error, success) {
if (error) {
console.log('SMTP is not working, check your configuration.');
return ``;
} else {
console.log('SMTP is correctly configured.');
}
});
}
module.exports = logSmtpStatus;

View File

@ -1,20 +0,0 @@
const transporter = require('./transporter');
const nodemailer = require('nodemailer');
function sendEmail(message) {
transporter.sendMail(message, (error, info) => {
if (error) {
console.log('Error occurred');
console.log(error.message);
return process.exit(1);
}
console.log('Message sent successfully!');
console.log(nodemailer.getTestMessageUrl(info));
// only needed when using pooled connections
transporter.close();
});
}
module.exports = sendEmail;

View File

@ -1,41 +0,0 @@
const nodemailer = require('nodemailer');
const {
smtpPool,
smtpHost,
smtpPort,
smtpUsername,
smtpPassword,
smtpSecure,
smtpRequireTLS,
} = require('../../config/config');
let mailConfig;
if (process.env.NODE_ENV === 'production') {
// all emails are delivered to destination
mailConfig = {
pool: smtpPool,
host: smtpHost,
port: smtpPort,
requireTLS: smtpRequireTLS,
secure: smtpSecure,
auth: {
user: smtpUsername,
pass: smtpPassword,
},
};
} else {
// all emails are catched by ethereal.email
// Check test emails: https://ethereal.email/login
mailConfig = {
host: 'smtp.ethereal.email',
port: 587,
auth: {
user: 'lemuel.wunsch91@ethereal.email',
pass: 'Y9Vs51v27Q9X9feJgN',
},
};
}
module.exports = nodemailer.createTransport(mailConfig);