initial commit
This commit is contained in:
commit
3236b348c5
28
.example.env
Normal file
28
.example.env
Normal file
@ -0,0 +1,28 @@
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=yourpassword
|
||||
DB_NAME=guardian_ai
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your_jwt_secret
|
||||
JWT_EXPIRES_IN=1d
|
||||
|
||||
# Stripe
|
||||
STRIPE_SECRET_KEY=your_stripe_secret
|
||||
STRIPE_WEBHOOK_SECRET=your_stripe_webhook_secret
|
||||
|
||||
# Nodemailer (Email)
|
||||
EMAIL_HOST=smtp.example.com
|
||||
EMAIL_PORT=587
|
||||
EMAIL_USER=your@email.com
|
||||
EMAIL_PASS=your_email_password
|
||||
EMAIL_FROM="Guardian AI <noreply@guardianai.com>"
|
||||
|
||||
# App
|
||||
PORT=3000
|
||||
CLIENT_URL=http://localhost:3000
|
||||
|
||||
# Other
|
||||
NODE_ENV=development
|
||||
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Debug
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS & Editor settings
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.vscode/
|
||||
.idea/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
build/
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Database files (if local DB is used)
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Coverage / testing
|
||||
coverage/
|
||||
.nyc_output/
|
||||
jest-*
|
||||
|
||||
# Misc
|
||||
*.local
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
33
package.json
Normal file
33
package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "guardian-ai-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Production-ready Node.js backend for Guardian AI (Express, MySQL, Sequelize, JWT, Stripe, Socket.IO)",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"migrate": "sequelize-cli db:migrate",
|
||||
"seed": "sequelize-cli db:seed:all"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"helmet": "^7.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.6.0",
|
||||
"nodemailer": "^6.9.8",
|
||||
"sequelize": "^6.37.1",
|
||||
"socket.io": "^4.7.5",
|
||||
"stripe": "^14.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.3",
|
||||
"sequelize-cli": "^6.6.1"
|
||||
},
|
||||
"author": "Guardian AI Team",
|
||||
"license": "MIT"
|
||||
}
|
||||
57
server.js
Normal file
57
server.js
Normal file
@ -0,0 +1,57 @@
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const morgan = require('morgan');
|
||||
const dotenv = require('dotenv');
|
||||
const { app: appConfig } = require('./src/config/config');
|
||||
const db = require('./src/models');
|
||||
const socketio = require('socket.io');
|
||||
const path = require('path');
|
||||
|
||||
// Load env
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = socketio(server, {
|
||||
cors: {
|
||||
origin: appConfig.clientUrl,
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
require('./src/sockets')(io);
|
||||
|
||||
// Middleware
|
||||
app.use(cors({ origin: appConfig.clientUrl, credentials: true }));
|
||||
app.use(helmet());
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
|
||||
// Serve static files from public
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// Attach io to app for use in routes/controllers
|
||||
app.set('io', io);
|
||||
|
||||
// Health check
|
||||
app.get('/api/health', (req, res) => res.json({ status: 'ok' }));
|
||||
|
||||
// Mount all API routes
|
||||
app.use('/api/v1', require('./src/routes'));
|
||||
|
||||
// Error handler
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err);
|
||||
res.status(err.status || 500).json({ error: err.message || 'Internal Server Error' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
const PORT = appConfig.port;
|
||||
db.sequelize.sync().then(() => {
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
});
|
||||
33
src/config/config.js
Normal file
33
src/config/config.js
Normal file
@ -0,0 +1,33 @@
|
||||
require('dotenv').config();
|
||||
|
||||
module.exports = {
|
||||
app: {
|
||||
port: process.env.PORT || 3000,
|
||||
clientUrl: process.env.CLIENT_URL || 'http://localhost:3000',
|
||||
env: process.env.NODE_ENV || 'development',
|
||||
},
|
||||
db: {
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT || 3306,
|
||||
dialect: 'mysql',
|
||||
logging: false,
|
||||
},
|
||||
jwt: {
|
||||
secret: process.env.JWT_SECRET,
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '1d',
|
||||
},
|
||||
stripe: {
|
||||
secretKey: process.env.STRIPE_SECRET_KEY,
|
||||
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
||||
},
|
||||
email: {
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT,
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS,
|
||||
from: process.env.EMAIL_FROM,
|
||||
},
|
||||
};
|
||||
16
src/config/database.js
Normal file
16
src/config/database.js
Normal file
@ -0,0 +1,16 @@
|
||||
const { Sequelize } = require('sequelize');
|
||||
const config = require('./config').db;
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
config.database,
|
||||
config.username,
|
||||
config.password,
|
||||
{
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
dialect: config.dialect,
|
||||
logging: config.logging,
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = sequelize;
|
||||
20
src/config/swagger.js
Normal file
20
src/config/swagger.js
Normal file
@ -0,0 +1,20 @@
|
||||
const swaggerJSDoc = require('swagger-jsdoc');
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'Guardian AI API',
|
||||
version: '1.0.0',
|
||||
description: 'API documentation for Guardian AI backend',
|
||||
},
|
||||
servers: [
|
||||
{ url: 'http://localhost:3000/api' },
|
||||
],
|
||||
},
|
||||
apis: ['./routes/*.js'],
|
||||
};
|
||||
|
||||
const swaggerSpec = swaggerJSDoc(options);
|
||||
|
||||
module.exports = swaggerSpec;
|
||||
10
src/controllers/alertsController.js
Normal file
10
src/controllers/alertsController.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Alerts Controller
|
||||
exports.list = async (req, res, next) => {
|
||||
// TODO: List all alerts for caregiver
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.markRead = async (req, res, next) => {
|
||||
// TODO: Mark alert as read
|
||||
res.json({});
|
||||
};
|
||||
46
src/controllers/authController.js
Normal file
46
src/controllers/authController.js
Normal file
@ -0,0 +1,46 @@
|
||||
const { User } = require('../models');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { jwt: jwtConfig } = require('../config/config');
|
||||
|
||||
exports.register = async (req, res, next) => {
|
||||
try {
|
||||
const { name, email, password, role } = req.body;
|
||||
const existing = await User.findOne({ where: { email } });
|
||||
if (existing) return res.status(409).json({ error: 'Email already in use' });
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
const user = await User.create({ name, email, password: hash, role });
|
||||
res.status(201).json({ id: user.id, name: user.name, email: user.email, role: user.role });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.login = async (req, res, next) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
const user = await User.findOne({ where: { email } });
|
||||
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
|
||||
const match = await bcrypt.compare(password, user.password);
|
||||
if (!match) return res.status(401).json({ error: 'Invalid credentials' });
|
||||
const token = jwt.sign({ id: user.id, role: user.role }, jwtConfig.secret, { expiresIn: jwtConfig.expiresIn });
|
||||
res.json({ token, user: { id: user.id, name: user.name, email: user.email, role: user.role } });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateRole = async (req, res, next) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const { role } = req.body;
|
||||
if (!['admin', 'caregiver', 'patient'].includes(role)) return res.status(400).json({ error: 'Invalid role' });
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) return res.status(404).json({ error: 'User not found' });
|
||||
user.role = role;
|
||||
await user.save();
|
||||
res.json({ id: user.id, role: user.role });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
15
src/controllers/callsController.js
Normal file
15
src/controllers/callsController.js
Normal file
@ -0,0 +1,15 @@
|
||||
// Calls Controller
|
||||
exports.create = async (req, res, next) => {
|
||||
// TODO: Create a new call record
|
||||
res.status(201).json({});
|
||||
};
|
||||
|
||||
exports.report = async (req, res, next) => {
|
||||
// TODO: Return call reports (per day, week, month, custom)
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.analytics = async (req, res, next) => {
|
||||
// TODO: Return call success/failure analytics
|
||||
res.json({ success: 0, fail: 0 });
|
||||
};
|
||||
35
src/controllers/caregiversController.js
Normal file
35
src/controllers/caregiversController.js
Normal file
@ -0,0 +1,35 @@
|
||||
// Caregivers Controller
|
||||
exports.list = async (req, res, next) => {
|
||||
// TODO: List all caregivers
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.create = async (req, res, next) => {
|
||||
// TODO: Create a new caregiver
|
||||
res.status(201).json({});
|
||||
};
|
||||
|
||||
exports.get = async (req, res, next) => {
|
||||
// TODO: Get caregiver by ID
|
||||
res.json({});
|
||||
};
|
||||
|
||||
exports.update = async (req, res, next) => {
|
||||
// TODO: Update caregiver by ID
|
||||
res.json({});
|
||||
};
|
||||
|
||||
exports.remove = async (req, res, next) => {
|
||||
// TODO: Delete caregiver by ID
|
||||
res.status(204).send();
|
||||
};
|
||||
|
||||
exports.total = async (req, res, next) => {
|
||||
// TODO: Return total caregivers count
|
||||
res.json({ total: 0 });
|
||||
};
|
||||
|
||||
exports.associatePatient = async (req, res, next) => {
|
||||
// TODO: Associate caregiver with patient
|
||||
res.json({});
|
||||
};
|
||||
10
src/controllers/configController.js
Normal file
10
src/controllers/configController.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Config Controller
|
||||
exports.listCallTypes = async (req, res, next) => {
|
||||
// TODO: List all call types
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.createCallType = async (req, res, next) => {
|
||||
// TODO: Create a new call type
|
||||
res.status(201).json({});
|
||||
};
|
||||
10
src/controllers/messagesController.js
Normal file
10
src/controllers/messagesController.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Messages Controller
|
||||
exports.list = async (req, res, next) => {
|
||||
// TODO: List all example messages
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.create = async (req, res, next) => {
|
||||
// TODO: Create a new example message
|
||||
res.status(201).json({});
|
||||
};
|
||||
30
src/controllers/patientsController.js
Normal file
30
src/controllers/patientsController.js
Normal file
@ -0,0 +1,30 @@
|
||||
// Patients Controller
|
||||
exports.list = async (req, res, next) => {
|
||||
// TODO: List all patients
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.create = async (req, res, next) => {
|
||||
// TODO: Create a new patient
|
||||
res.status(201).json({});
|
||||
};
|
||||
|
||||
exports.get = async (req, res, next) => {
|
||||
// TODO: Get patient by ID
|
||||
res.json({});
|
||||
};
|
||||
|
||||
exports.update = async (req, res, next) => {
|
||||
// TODO: Update patient by ID
|
||||
res.json({});
|
||||
};
|
||||
|
||||
exports.remove = async (req, res, next) => {
|
||||
// TODO: Delete patient by ID
|
||||
res.status(204).send();
|
||||
};
|
||||
|
||||
exports.total = async (req, res, next) => {
|
||||
// TODO: Return total patients count
|
||||
res.json({ total: 0 });
|
||||
};
|
||||
10
src/controllers/settingsController.js
Normal file
10
src/controllers/settingsController.js
Normal file
@ -0,0 +1,10 @@
|
||||
// Settings Controller
|
||||
exports.getNotificationSetting = async (req, res, next) => {
|
||||
// TODO: Get caregiver's weekly summary notification setting
|
||||
res.json({ notificationOptIn: true });
|
||||
};
|
||||
|
||||
exports.setNotificationSetting = async (req, res, next) => {
|
||||
// TODO: Set caregiver's weekly summary notification setting
|
||||
res.json({});
|
||||
};
|
||||
5
src/controllers/statsController.js
Normal file
5
src/controllers/statsController.js
Normal file
@ -0,0 +1,5 @@
|
||||
// Stats Controller
|
||||
exports.systemStats = async (req, res, next) => {
|
||||
// TODO: Return system stats (SMS, emails, calls)
|
||||
res.json({ smsSent: 0, emailsSent: 0, callsMade: 0 });
|
||||
};
|
||||
16
src/controllers/stripeController.js
Normal file
16
src/controllers/stripeController.js
Normal file
@ -0,0 +1,16 @@
|
||||
const stripeService = require('../services/stripeService');
|
||||
|
||||
exports.subscribe = async (req, res, next) => {
|
||||
// TODO: Create Stripe subscription for caregiver
|
||||
res.status(201).json({});
|
||||
};
|
||||
|
||||
exports.invoices = async (req, res, next) => {
|
||||
// TODO: List invoices for caregiver
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.webhook = (req, res) => {
|
||||
// TODO: Handle Stripe webhook events
|
||||
stripeService.handleWebhook(req, res);
|
||||
};
|
||||
20
src/controllers/tiersController.js
Normal file
20
src/controllers/tiersController.js
Normal file
@ -0,0 +1,20 @@
|
||||
// Tiers Controller
|
||||
exports.list = async (req, res, next) => {
|
||||
// TODO: List all tiers
|
||||
res.json([]);
|
||||
};
|
||||
|
||||
exports.create = async (req, res, next) => {
|
||||
// TODO: Create a new tier
|
||||
res.status(201).json({});
|
||||
};
|
||||
|
||||
exports.update = async (req, res, next) => {
|
||||
// TODO: Update tier by ID
|
||||
res.json({});
|
||||
};
|
||||
|
||||
exports.remove = async (req, res, next) => {
|
||||
// TODO: Delete tier by ID
|
||||
res.status(204).send();
|
||||
};
|
||||
24
src/middlewares/auth.js
Normal file
24
src/middlewares/auth.js
Normal file
@ -0,0 +1,24 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { jwt: jwtConfig } = require('../config/config');
|
||||
|
||||
exports.authenticateJWT = (req, res, next) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'No token provided' });
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, jwtConfig.secret);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
};
|
||||
|
||||
exports.authorizeRoles = (...roles) => (req, res, next) => {
|
||||
if (!req.user || !roles.includes(req.user.role)) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
47
src/migrations/20240601-create-alert.js
Normal file
47
src/migrations/20240601-create-alert.js
Normal file
@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('alerts', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
caregiverId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
patientId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
message: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
isRead: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('alerts');
|
||||
},
|
||||
};
|
||||
30
src/migrations/20240601-create-call-type.js
Normal file
30
src/migrations/20240601-create-call-type.js
Normal file
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('call_types', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('call_types');
|
||||
},
|
||||
};
|
||||
59
src/migrations/20240601-create-call.js
Normal file
59
src/migrations/20240601-create-call.js
Normal file
@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('calls', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
caregiverId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
patientId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
timestamp: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('success', 'fail'),
|
||||
allowNull: false,
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('voice', 'video'),
|
||||
allowNull: false,
|
||||
},
|
||||
notes: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
duration: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('calls');
|
||||
},
|
||||
};
|
||||
39
src/migrations/20240601-create-caregiver-patient.js
Normal file
39
src/migrations/20240601-create-caregiver-patient.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('caregiver_patients', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
caregiverId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
patientId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('caregiver_patients');
|
||||
},
|
||||
};
|
||||
29
src/migrations/20240601-create-example-message.js
Normal file
29
src/migrations/20240601-create-example-message.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('example_messages', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
text: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('example_messages');
|
||||
},
|
||||
};
|
||||
44
src/migrations/20240601-create-llm-comment.js
Normal file
44
src/migrations/20240601-create-llm-comment.js
Normal file
@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('llm_comments', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
patientId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
transcript: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
flagged: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
flaggedReason: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('llm_comments');
|
||||
},
|
||||
};
|
||||
37
src/migrations/20240601-create-system-stat.js
Normal file
37
src/migrations/20240601-create-system-stat.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('system_stats', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
smsSent: {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
},
|
||||
emailsSent: {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
},
|
||||
callsMade: {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('system_stats');
|
||||
},
|
||||
};
|
||||
41
src/migrations/20240601-create-tier.js
Normal file
41
src/migrations/20240601-create-tier.js
Normal file
@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('tiers', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
contactsLimit: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
price: {
|
||||
type: Sequelize.DECIMAL(10,2),
|
||||
allowNull: false,
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('tiers');
|
||||
},
|
||||
};
|
||||
54
src/migrations/20240601-create-user.js
Normal file
54
src/migrations/20240601-create-user.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('users', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
email: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
role: {
|
||||
type: Sequelize.ENUM('admin', 'caregiver', 'patient'),
|
||||
allowNull: false,
|
||||
defaultValue: 'caregiver',
|
||||
},
|
||||
notificationOptIn: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: true,
|
||||
},
|
||||
tierId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: { model: 'tiers', key: 'id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
});
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('users');
|
||||
},
|
||||
};
|
||||
131
src/migrations/20240601-seed-initial-data.js
Normal file
131
src/migrations/20240601-seed-initial-data.js
Normal file
@ -0,0 +1,131 @@
|
||||
'use strict';
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// Insert tiers
|
||||
await queryInterface.bulkInsert('tiers', [
|
||||
{
|
||||
name: 'Basic',
|
||||
contactsLimit: 3,
|
||||
price: 9.99,
|
||||
description: 'Basic plan',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
name: 'Premium',
|
||||
contactsLimit: 10,
|
||||
price: 29.99,
|
||||
description: 'Premium plan',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
]);
|
||||
|
||||
// Get tier IDs
|
||||
const tiers = await queryInterface.sequelize.query('SELECT id FROM tiers;');
|
||||
const tierRows = tiers[0];
|
||||
|
||||
// Insert caregivers
|
||||
const password = await bcrypt.hash('password123', 10);
|
||||
await queryInterface.bulkInsert('users', [
|
||||
{
|
||||
name: 'Caregiver One',
|
||||
email: 'caregiver1@example.com',
|
||||
password,
|
||||
role: 'caregiver',
|
||||
notificationOptIn: true,
|
||||
tierId: tierRows[0].id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
name: 'Caregiver Two',
|
||||
email: 'caregiver2@example.com',
|
||||
password,
|
||||
role: 'caregiver',
|
||||
notificationOptIn: true,
|
||||
tierId: tierRows[1].id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
]);
|
||||
|
||||
// Insert patients
|
||||
await queryInterface.bulkInsert('users', [
|
||||
{
|
||||
name: 'Patient One',
|
||||
email: 'patient1@example.com',
|
||||
password,
|
||||
role: 'patient',
|
||||
notificationOptIn: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
name: 'Patient Two',
|
||||
email: 'patient2@example.com',
|
||||
password,
|
||||
role: 'patient',
|
||||
notificationOptIn: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
]);
|
||||
|
||||
// Get user IDs
|
||||
const users = await queryInterface.sequelize.query('SELECT id, role FROM users;');
|
||||
const caregivers = users[0].filter(u => u.role === 'caregiver');
|
||||
const patients = users[0].filter(u => u.role === 'patient');
|
||||
|
||||
// Associate caregivers and patients
|
||||
await queryInterface.bulkInsert('caregiver_patients', [
|
||||
{
|
||||
caregiverId: caregivers[0].id,
|
||||
patientId: patients[0].id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
caregiverId: caregivers[1].id,
|
||||
patientId: patients[1].id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
]);
|
||||
|
||||
// Insert calls
|
||||
await queryInterface.bulkInsert('calls', [
|
||||
{
|
||||
caregiverId: caregivers[0].id,
|
||||
patientId: patients[0].id,
|
||||
timestamp: new Date(),
|
||||
status: 'success',
|
||||
type: 'voice',
|
||||
notes: 'Routine check-in',
|
||||
duration: 300,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
caregiverId: caregivers[1].id,
|
||||
patientId: patients[1].id,
|
||||
timestamp: new Date(),
|
||||
status: 'fail',
|
||||
type: 'video',
|
||||
notes: 'Missed call',
|
||||
duration: 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.bulkDelete('calls', null, {});
|
||||
await queryInterface.bulkDelete('caregiver_patients', null, {});
|
||||
await queryInterface.bulkDelete('users', null, {});
|
||||
await queryInterface.bulkDelete('tiers', null, {});
|
||||
},
|
||||
};
|
||||
35
src/models/alert.js
Normal file
35
src/models/alert.js
Normal file
@ -0,0 +1,35 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Alert = sequelize.define('Alert', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
caregiverId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
patientId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
isRead: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
}, {
|
||||
tableName: 'alerts',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
Alert.associate = (models) => {
|
||||
Alert.belongsTo(models.User, { foreignKey: 'caregiverId', as: 'caregiver' });
|
||||
Alert.belongsTo(models.User, { foreignKey: 'patientId', as: 'patient' });
|
||||
};
|
||||
|
||||
return Alert;
|
||||
};
|
||||
48
src/models/call.js
Normal file
48
src/models/call.js
Normal file
@ -0,0 +1,48 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Call = sequelize.define('Call', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
caregiverId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
patientId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
timestamp: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('success', 'fail'),
|
||||
allowNull: false,
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('voice', 'video'),
|
||||
allowNull: false,
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Duration in seconds',
|
||||
},
|
||||
}, {
|
||||
tableName: 'calls',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
Call.associate = (models) => {
|
||||
Call.belongsTo(models.User, { foreignKey: 'caregiverId', as: 'caregiver' });
|
||||
Call.belongsTo(models.User, { foreignKey: 'patientId', as: 'patient' });
|
||||
};
|
||||
|
||||
return Call;
|
||||
};
|
||||
19
src/models/callType.js
Normal file
19
src/models/callType.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const CallType = sequelize.define('CallType', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
}, {
|
||||
tableName: 'call_types',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
return CallType;
|
||||
};
|
||||
27
src/models/caregiverPatient.js
Normal file
27
src/models/caregiverPatient.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const CaregiverPatient = sequelize.define('CaregiverPatient', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
caregiverId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
patientId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
tableName: 'caregiver_patients',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
CaregiverPatient.associate = (models) => {
|
||||
CaregiverPatient.belongsTo(models.User, { foreignKey: 'caregiverId', as: 'caregiver' });
|
||||
CaregiverPatient.belongsTo(models.User, { foreignKey: 'patientId', as: 'patient' });
|
||||
};
|
||||
|
||||
return CaregiverPatient;
|
||||
};
|
||||
18
src/models/exampleMessage.js
Normal file
18
src/models/exampleMessage.js
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const ExampleMessage = sequelize.define('ExampleMessage', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
text: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
tableName: 'example_messages',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
return ExampleMessage;
|
||||
};
|
||||
24
src/models/index.js
Normal file
24
src/models/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
|
||||
const db = {};
|
||||
|
||||
fs.readdirSync(__dirname)
|
||||
.filter(file => file !== 'index.js' && file.endsWith('.js'))
|
||||
.forEach(file => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
module.exports = db;
|
||||
34
src/models/llmComment.js
Normal file
34
src/models/llmComment.js
Normal file
@ -0,0 +1,34 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const LLMComment = sequelize.define('LLMComment', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
patientId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
transcript: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
flagged: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
flaggedReason: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
tableName: 'llm_comments',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
LLMComment.associate = (models) => {
|
||||
LLMComment.belongsTo(models.User, { foreignKey: 'patientId', as: 'patient' });
|
||||
};
|
||||
|
||||
return LLMComment;
|
||||
};
|
||||
26
src/models/systemStat.js
Normal file
26
src/models/systemStat.js
Normal file
@ -0,0 +1,26 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const SystemStat = sequelize.define('SystemStat', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
smsSent: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
},
|
||||
emailsSent: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
},
|
||||
callsMade: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
},
|
||||
}, {
|
||||
tableName: 'system_stats',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
return SystemStat;
|
||||
};
|
||||
11
src/routes/alerts.js
Normal file
11
src/routes/alerts.js
Normal file
@ -0,0 +1,11 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const alertsController = require('../controllers/alertsController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
router.use(authenticateJWT, authorizeRoles('caregiver'));
|
||||
|
||||
router.get('/', alertsController.list);
|
||||
router.post('/mark-read/:id', alertsController.markRead);
|
||||
|
||||
module.exports = router;
|
||||
23
src/routes/auth.js
Normal file
23
src/routes/auth.js
Normal file
@ -0,0 +1,23 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const authController = require('../controllers/authController');
|
||||
const { body } = require('express-validator');
|
||||
|
||||
// Register
|
||||
router.post('/register', [
|
||||
body('name').notEmpty(),
|
||||
body('email').isEmail(),
|
||||
body('password').isLength({ min: 6 }),
|
||||
body('role').isIn(['admin', 'caregiver', 'patient'])
|
||||
], authController.register);
|
||||
|
||||
// Login
|
||||
router.post('/login', [
|
||||
body('email').isEmail(),
|
||||
body('password').notEmpty()
|
||||
], authController.login);
|
||||
|
||||
// Role management (admin only)
|
||||
router.patch('/role/:userId', authController.updateRole);
|
||||
|
||||
module.exports = router;
|
||||
12
src/routes/calls.js
Normal file
12
src/routes/calls.js
Normal file
@ -0,0 +1,12 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const callsController = require('../controllers/callsController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
router.use(authenticateJWT, authorizeRoles('admin', 'caregiver'));
|
||||
|
||||
router.post('/', callsController.create);
|
||||
router.get('/report', callsController.report);
|
||||
router.get('/analytics', callsController.analytics);
|
||||
|
||||
module.exports = router;
|
||||
17
src/routes/caregivers.js
Normal file
17
src/routes/caregivers.js
Normal file
@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const caregiversController = require('../controllers/caregiversController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
// All routes require caregiver or admin
|
||||
router.use(authenticateJWT, authorizeRoles('admin', 'caregiver'));
|
||||
|
||||
router.get('/', caregiversController.list);
|
||||
router.post('/', caregiversController.create);
|
||||
router.get('/:id', caregiversController.get);
|
||||
router.put('/:id', caregiversController.update);
|
||||
router.delete('/:id', caregiversController.remove);
|
||||
router.get('/stats/total', caregiversController.total);
|
||||
router.post('/:id/associate-patient', caregiversController.associatePatient);
|
||||
|
||||
module.exports = router;
|
||||
11
src/routes/config.js
Normal file
11
src/routes/config.js
Normal file
@ -0,0 +1,11 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const configController = require('../controllers/configController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
router.use(authenticateJWT);
|
||||
|
||||
router.get('/call-types', authorizeRoles('admin', 'caregiver'), configController.listCallTypes);
|
||||
router.post('/call-types', authorizeRoles('admin'), configController.createCallType);
|
||||
|
||||
module.exports = router;
|
||||
1
src/routes/docs.js
Normal file
1
src/routes/docs.js
Normal file
@ -0,0 +1 @@
|
||||
|
||||
17
src/routes/index.js
Normal file
17
src/routes/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.use('/auth', require('./auth'));
|
||||
router.use('/caregivers', require('./caregivers'));
|
||||
router.use('/patients', require('./patients'));
|
||||
router.use('/calls', require('./calls'));
|
||||
router.use('/tiers', require('./tiers'));
|
||||
router.use('/messages', require('./messages'));
|
||||
router.use('/config', require('./config'));
|
||||
router.use('/stats', require('./stats'));
|
||||
router.use('/alerts', require('./alerts'));
|
||||
router.use('/settings', require('./settings'));
|
||||
router.use('/docs', require('./docs'));
|
||||
router.use('/stripe', require('./stripe'));
|
||||
|
||||
module.exports = router;
|
||||
11
src/routes/messages.js
Normal file
11
src/routes/messages.js
Normal file
@ -0,0 +1,11 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const messagesController = require('../controllers/messagesController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
router.use(authenticateJWT);
|
||||
|
||||
router.get('/', authorizeRoles('admin', 'caregiver'), messagesController.list);
|
||||
router.post('/', authorizeRoles('admin'), messagesController.create);
|
||||
|
||||
module.exports = router;
|
||||
16
src/routes/patients.js
Normal file
16
src/routes/patients.js
Normal file
@ -0,0 +1,16 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const patientsController = require('../controllers/patientsController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
// All routes require caregiver or admin
|
||||
router.use(authenticateJWT, authorizeRoles('admin', 'caregiver'));
|
||||
|
||||
router.get('/', patientsController.list);
|
||||
router.post('/', patientsController.create);
|
||||
router.get('/:id', patientsController.get);
|
||||
router.put('/:id', patientsController.update);
|
||||
router.delete('/:id', patientsController.remove);
|
||||
router.get('/stats/total', patientsController.total);
|
||||
|
||||
module.exports = router;
|
||||
11
src/routes/settings.js
Normal file
11
src/routes/settings.js
Normal file
@ -0,0 +1,11 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const settingsController = require('../controllers/settingsController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
router.use(authenticateJWT, authorizeRoles('caregiver'));
|
||||
|
||||
router.get('/notifications', settingsController.getNotificationSetting);
|
||||
router.post('/notifications', settingsController.setNotificationSetting);
|
||||
|
||||
module.exports = router;
|
||||
10
src/routes/stats.js
Normal file
10
src/routes/stats.js
Normal file
@ -0,0 +1,10 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const statsController = require('../controllers/statsController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
router.use(authenticateJWT, authorizeRoles('admin'));
|
||||
|
||||
router.get('/system', statsController.systemStats);
|
||||
|
||||
module.exports = router;
|
||||
1
src/routes/stripe.js
Normal file
1
src/routes/stripe.js
Normal file
@ -0,0 +1 @@
|
||||
|
||||
13
src/routes/tiers.js
Normal file
13
src/routes/tiers.js
Normal file
@ -0,0 +1,13 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const tiersController = require('../controllers/tiersController');
|
||||
const { authenticateJWT, authorizeRoles } = require('../middlewares/auth');
|
||||
|
||||
router.use(authenticateJWT, authorizeRoles('admin'));
|
||||
|
||||
router.get('/', tiersController.list);
|
||||
router.post('/', tiersController.create);
|
||||
router.put('/:id', tiersController.update);
|
||||
router.delete('/:id', tiersController.remove);
|
||||
|
||||
module.exports = router;
|
||||
26
src/services/stripeService.js
Normal file
26
src/services/stripeService.js
Normal file
@ -0,0 +1,26 @@
|
||||
const Stripe = require('stripe');
|
||||
const { stripe: stripeConfig } = require('../config/config');
|
||||
|
||||
const stripe = Stripe(stripeConfig.secretKey);
|
||||
|
||||
exports.createCustomer = async (email, name) => {
|
||||
return await stripe.customers.create({ email, name });
|
||||
};
|
||||
|
||||
exports.createSubscription = async (customerId, priceId) => {
|
||||
return await stripe.subscriptions.create({
|
||||
customer: customerId,
|
||||
items: [{ price: priceId }],
|
||||
payment_behavior: 'default_incomplete',
|
||||
expand: ['latest_invoice.payment_intent'],
|
||||
});
|
||||
};
|
||||
|
||||
exports.getInvoices = async (customerId) => {
|
||||
return await stripe.invoices.list({ customer: customerId });
|
||||
};
|
||||
|
||||
exports.handleWebhook = (req, res) => {
|
||||
// TODO: Handle Stripe webhook events
|
||||
res.status(200).send('Webhook received');
|
||||
};
|
||||
13
src/services/weeklySummaryService.js
Normal file
13
src/services/weeklySummaryService.js
Normal file
@ -0,0 +1,13 @@
|
||||
const { sendMail } = require('../utils/email');
|
||||
// const cron = require('node-cron'); // Uncomment if scheduling
|
||||
|
||||
exports.sendWeeklySummary = async (caregiver, stats) => {
|
||||
const subject = 'Your Weekly Call Summary';
|
||||
const text = `Hello ${caregiver.name},\n\nHere is your weekly call summary:\nCalls made: ${stats.calls}\n`;
|
||||
await sendMail(caregiver.email, subject, text);
|
||||
};
|
||||
|
||||
// Placeholder for scheduling
|
||||
// cron.schedule('0 8 * * 1', async () => {
|
||||
// // Fetch caregivers who opted in and send summary
|
||||
// });
|
||||
7
src/sockets/alerts.js
Normal file
7
src/sockets/alerts.js
Normal file
@ -0,0 +1,7 @@
|
||||
// Socket.IO Alerts
|
||||
const emitAlert = (io, caregiverId, alert) => {
|
||||
// In production, map caregiverId to socket.id (e.g., via a map or DB)
|
||||
io.to(`caregiver_${caregiverId}`).emit('alert', alert);
|
||||
};
|
||||
|
||||
module.exports = { emitAlert };
|
||||
1
src/sockets/index.js
Normal file
1
src/sockets/index.js
Normal file
@ -0,0 +1 @@
|
||||
|
||||
21
src/utils/email.js
Normal file
21
src/utils/email.js
Normal file
@ -0,0 +1,21 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
const { email: emailConfig } = require('../config/config');
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: emailConfig.host,
|
||||
port: emailConfig.port,
|
||||
auth: {
|
||||
user: emailConfig.user,
|
||||
pass: emailConfig.pass,
|
||||
},
|
||||
});
|
||||
|
||||
exports.sendMail = async (to, subject, text, html) => {
|
||||
return transporter.sendMail({
|
||||
from: emailConfig.from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
});
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user