Compare commits
No commits in common. "master" and "no-orm-incomplete" have entirely different histories.
master
...
no-orm-inc
63
README.md
63
README.md
@ -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)
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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 });
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
||||
|
@ -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',
|
||||
};
|
@ -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
5
data/dbConfig.js
Normal file
@ -0,0 +1,5 @@
|
||||
const pgp = require('pg-promise')();
|
||||
|
||||
const db = pgp(process.env.DATABASE_URL);
|
||||
|
||||
module.exports = db;
|
5
data/how_to_do_migration.md
Normal file
5
data/how_to_do_migration.md
Normal 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`
|
@ -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');
|
||||
};
|
@ -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');
|
||||
};
|
@ -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');
|
||||
};
|
@ -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');
|
||||
};
|
@ -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');
|
||||
};
|
@ -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);
|
||||
});
|
||||
};
|
@ -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',
|
||||
},
|
||||
]);
|
||||
});
|
||||
};
|
@ -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',
|
||||
},
|
||||
]);
|
||||
});
|
||||
};
|
@ -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,
|
||||
},
|
||||
]);
|
||||
});
|
||||
};
|
@ -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',
|
||||
},
|
||||
]);
|
||||
});
|
||||
};
|
@ -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;
|
11
index.js
11
index.js
@ -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}`),
|
||||
);
|
||||
|
34
knexfile.js
34
knexfile.js
@ -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,
|
||||
},
|
||||
};
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
97
migrations/0_initial-migration.js
Normal file
97
migrations/0_initial-migration.js
Normal 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
3210
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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);
|
Loading…
Reference in New Issue
Block a user