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.
|
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.
|
It's based on the availibity of all participants for specific days.
|
||||||
|
|
||||||
- [Documentation](https://git.ruihildt.xyz/meeting-planner/documentation)
|
- [Documentation](https://git.armada.digital/meeting-planner/documentation)
|
||||||
- [Frontend](https://git.ruihildt.xyz/meeting-planner/frontend)
|
- Frontend (coming soon)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**URL** https://meeting-planner-backend.herokuapp.com/
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Backend architecture
|
# 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)
|
- [Database design](https://dbdiagram.io/d/5e769ab14495b02c3b88936f)
|
||||||
- [Flowchart](https://app.diagrams.net/#Uhttps://git.ruihildt.xyz/meeting-planner/documentation/raw/branch/master/meetingscheduler.drawio)
|
- [Flowchart](https://app.diagrams.net/#Uhttps://git.armada.digital/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 |
|
|
||||||
|
@ -1,84 +1,14 @@
|
|||||||
const db = require('../../data/db');
|
const db = require('../../data/dbConfig');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
addAccount,
|
addUser,
|
||||||
getAccountById,
|
|
||||||
updateAccount,
|
|
||||||
deleteAccount,
|
|
||||||
getMeetingsByAccountId,
|
|
||||||
getAccountByEmail,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function addAccount(data) {
|
function addUser(userData) {
|
||||||
return db('account')
|
// TODO Complete query without providing id
|
||||||
.insert(data)
|
// right now if ID is not provided, pg-promise send an erro fo mising column
|
||||||
.returning([
|
return db.one(
|
||||||
'id',
|
'INSERT INTO account VALUES(emptyUpdate, ${username}, ${email}, ${password}, ${timezone}, ${earliest_time}, ${latest_time}) RETURNING *',
|
||||||
'username',
|
userData,
|
||||||
'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',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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');
|
let express = require('express');
|
||||||
const router = express.Router();
|
let router = express.Router();
|
||||||
const bcrypt = require('bcryptjs');
|
|
||||||
|
|
||||||
const { saltingRounds } = require('../../config/config');
|
let Account = require('../models/accountModel');
|
||||||
const { authenticate } = require('../../middlewares/authenticate');
|
|
||||||
const { validateAccountID } = require('../../middlewares/validateAccountID');
|
|
||||||
|
|
||||||
const Account = require('../models/accountModel');
|
// Add a user
|
||||||
|
|
||||||
// TODO : remove if unused
|
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
const data = { ...req.body };
|
const userData = { ...req.body };
|
||||||
const hash = bcrypt.hashSync(data.password, saltingRounds);
|
|
||||||
data.password = hash;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [account] = await Account.addAccount(data);
|
const user = await Account.addUser(userData);
|
||||||
res.status(201).json(account);
|
res.status(201).json(user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: 'Failed to add new account.', error });
|
res.status(500).json({ message: 'Failed to add new user', 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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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 express = require('express');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const cors = require('cors');
|
|
||||||
const helmet = require('helmet');
|
|
||||||
|
|
||||||
const logSmtpStatus = require('../services/email/logSmtpStatus');
|
|
||||||
|
|
||||||
const accountsRoute = require('./routes/accountRoute');
|
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();
|
const server = express();
|
||||||
|
|
||||||
server.use(cors());
|
|
||||||
server.use(express.json());
|
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/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) => {
|
server.get('/', (req, res) =>
|
||||||
res.json({
|
res.status(200).send('<h2>Welcome to Meeting Planner Backend API service.</h2>'),
|
||||||
message: `Meeting Planner Backend API :)`,
|
);
|
||||||
documentation: `Check out: https://git.ruihildt.xyz/meeting-planner/backend`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = server;
|
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');
|
require('dotenv').config()
|
||||||
const { port } = require('./config/config');
|
const app = require("./api/server");
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
server.listen(port, () =>
|
app.listen(PORT, () =>
|
||||||
console.log(
|
console.log(`Meeting Planner Backend listening at http://localhost:${PORT}`),
|
||||||
`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 });
|
||||||
|
// };
|
2064
package-lock.json
generated
2064
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -5,7 +5,8 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon index.js",
|
"dev": "nodemon index.js",
|
||||||
"start": "node index.js"
|
"prod": "node index.js",
|
||||||
|
"migrate": "node-pg-migrate"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -14,17 +15,10 @@
|
|||||||
"author": "rui hildt",
|
"author": "rui hildt",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcryptjs": "^2.4.3",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-validator": "^6.4.1",
|
"node-pg-migrate": "^4.7.0",
|
||||||
"helmet": "^3.22.0",
|
|
||||||
"jsonwebtoken": "^8.5.1",
|
|
||||||
"knex": "^0.21.1",
|
|
||||||
"nodemailer": "^6.4.11",
|
|
||||||
"pg": "^8.0.3",
|
"pg": "^8.0.3",
|
||||||
"pgtools": "^0.3.0",
|
"pg-promise": "^10.5.2"
|
||||||
"uuid": "^8.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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