diff --git a/src/api/routes/adminRoutes.js b/src/api/routes/adminRoutes.js
index 7a1d1e2..4baa5e4 100644
--- a/src/api/routes/adminRoutes.js
+++ b/src/api/routes/adminRoutes.js
@@ -16,6 +16,9 @@ export const adminGetUserIdsRoute = {
path: '/admin/allUserIds',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireAdmin],
description: 'Get all user IDs',
async method(req, res) {
@@ -23,9 +26,13 @@ export const adminGetUserIdsRoute = {
res.status(200);
res.write('[\n');
+ let first = true;
const users = DbUser.find().lean().cursor();
for await (const user of users) {
- res.write(JSON.stringify(user._id) + ',\n');
+ res.write(
+ (first ? '' : ',') + JSON.stringify(user._id) + '\n'
+ );
+ first = false;
}
res.write(']');
@@ -38,6 +45,9 @@ export const adminMonitorAllRoute = {
path: '/admin/monitorAllUsers',
methods: {
post: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireAdmin],
description: 'Monitor all users',
async method(req, res) {
diff --git a/src/api/routes/alarmRoutes.js b/src/api/routes/alarmRoutes.js
index aeae4ab..b0147ab 100644
--- a/src/api/routes/alarmRoutes.js
+++ b/src/api/routes/alarmRoutes.js
@@ -15,14 +15,20 @@ export const alarmByUserRoute = {
path: '/user/:id/alarm',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireMonitor],
description: 'Get the alarm for a monitored user, if one is set',
async method(req, res) {
const user = await getUserById(req.params.id);
- res.send(user.alarm);
+ res.send(user.alarm ?? 'null');
}
}),
delete: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireMonitor],
description: 'Clear the alarm for a monitored user',
async method(req, res) {
@@ -44,23 +50,27 @@ export const alarmListRoute = {
path: '/alarms',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireMonitor],
description: 'Get a list of all alarms for monitored users',
async method(req, res) {
// execute the query asynchronously and manually construct a response, for scaling reasons
const users = DbUser.find({
- _id: { $in: req.user.monitoredUsers }
+ _id: { $in: req.user.monitoredUsers },
+ alarm: { $exists: true, $ne: null }
})
.lean()
.cursor();
res.status(200);
res.write('{\n');
+ let first = true;
for await (const user of users) {
- if (user.alarm) {
- res.write(
- `"${user._id}": ${JSON.stringify(user.alarm)},\n`
- );
- }
+ res.write(
+ `${first ? '' : ','}"${user._id}": ${JSON.stringify(user.alarm)}\n`
+ );
+ first = false;
}
res.write('}');
res.end();
@@ -76,15 +86,24 @@ export const alarmRoute = {
path: '/alarm/@me',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireUser],
description: "Get the current user's alarm",
async method(req, res) {
- res.send(req.user.alarm);
+ res.send(req.user.alarm ?? 'null');
}
}),
put: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
+ exampleBody: {
+ reason: 'fall'
+ },
middlewares: [requireUser],
- description: 'Raise an alarm',
+ description: 'Raise an alarm (enum: one of "fall" or "toilet")',
async method(req, res) {
req.user.alarm = await AlarmDto.create(req.body);
await req.user.save();
@@ -92,6 +111,9 @@ export const alarmRoute = {
}
}),
delete: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireUser],
description: 'Clear alarm',
async method(req, res) {
diff --git a/src/api/routes/assignedUserRoutes.js b/src/api/routes/assignedUserRoutes.js
index dac9b13..e870cd8 100644
--- a/src/api/routes/assignedUserRoutes.js
+++ b/src/api/routes/assignedUserRoutes.js
@@ -10,6 +10,9 @@ export const assignedUserRoute = {
path: '/monitor/assignedUsers',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireMonitor],
description: 'Get assigned users',
async method(req, res) {
@@ -17,7 +20,13 @@ export const assignedUserRoute = {
}
}),
patch: {
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireMonitor],
+ exampleBody: {
+ userId: 'abcdef'
+ },
description: 'Add an assigned user by ID',
async method(req, res) {
if (!req.body.userId) {
@@ -41,6 +50,9 @@ export const assignedUserRoute = {
}
},
delete: {
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireMonitor],
description: 'Remove an assigned user by ID',
async method(req, res) {
diff --git a/src/api/routes/auth/accountRoutes.js b/src/api/routes/auth/accountRoutes.js
index 34592ed..f0d0102 100644
--- a/src/api/routes/auth/accountRoutes.js
+++ b/src/api/routes/auth/accountRoutes.js
@@ -3,6 +3,7 @@ import { AuthDto, RegisterDto } from '#dto/index.js';
import { RouteDescription, RouteMethod } from '#api/RouteDescription.js';
import { WhoAmIDto } from '#dto/auth/WhoAmIDto.js';
import { requireAuth } from '#api/middlewares/index.js';
+import { SafeNSoundError } from '#util/error.js';
/**
* @type {RouteDescription}
@@ -59,13 +60,17 @@ export const logoutRoute = {
path: '/auth/logout',
methods: {
post: new RouteMethod({
- description: 'Log out from a device (TODO)',
+ description: 'Log out from a device',
exampleHeaders: {
Authorization: 'Bearer {{accessToken}}'
},
+ middlewares: [requireAuth],
async method(req, res) {
- const data = await AuthDto.create(req.body);
- // const loginResult = await deleteDevice(data, );
+ const deviceIndex = req.user.devices.findIndex(
+ device => device.id === req.device._id
+ );
+ req.user.devices.splice(deviceIndex, 1);
+ await req.user.save();
res.status(204).send();
}
})
@@ -102,6 +107,9 @@ export const whoAmI = {
methods: {
get: new RouteMethod({
description: 'Get current user',
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireAuth],
async method(req, res) {
const data = await WhoAmIDto.create({
diff --git a/src/api/routes/auth/adminAccountRoutes.js b/src/api/routes/auth/adminAccountRoutes.js
deleted file mode 100644
index b485002..0000000
--- a/src/api/routes/auth/adminAccountRoutes.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
- DbUser,
- deleteUser,
- getUserById,
- loginUser,
- registerUser,
- UserType
-} from '#db/index.js';
-import { AuthDto, RegisterDto } from '#dto/index.js';
-import { requireAdmin, requireRole } from '#api/middlewares/index.js';
-import { RouteDescription, RouteMethod } from '#api/RouteDescription.js';
-
-/**
- * @type {RouteDescription}
- */
-export const adminGetUsersRoute = {
- path: '/admin/users',
- methods: {
- get: new RouteMethod({
- description: 'Get all users (raw)',
- exampleHeaders: {
- Authorization: 'Bearer {{accessToken}}'
- },
- middlewares: [requireAdmin],
- async method(req, res) {
- res.send(DbUser.find({}).exec());
- }
- })
- }
-};
-
-/**
- * @type {RouteDescription}
- */
-export const adminUserRoute = {
- path: '/admin/user/:id',
- methods: {
- get: new RouteMethod({
- description: 'Get a user (raw)',
- exampleHeaders: {
- Authorization: 'Bearer {{accessToken}}'
- },
- middlewares: [requireAdmin],
- async method(req, res) {
- const user = await getUserById(req.params.id);
- res.send(user);
- }
- }),
- delete: new RouteMethod({
- middlewares: [requireAdmin],
- description: 'Delete a user',
- async method(req, res) {
- await deleteUser(data);
- res.status(204).send();
- }
- })
- }
-};
diff --git a/src/api/routes/auth/deviceRoutes.js b/src/api/routes/auth/deviceRoutes.js
index 551252b..41802b8 100644
--- a/src/api/routes/auth/deviceRoutes.js
+++ b/src/api/routes/auth/deviceRoutes.js
@@ -1,7 +1,11 @@
-import { registerUser } from '#db/index.js';
-import { RegisterDto } from '#dto/index.js';
import { requireAuth } from '#api/middlewares/index.js';
import { RouteMethod } from '#api/RouteDescription.js';
+import { SafeNSoundError } from '#util/error.js';
+import Joi from 'joi';
+
+const deviceUpdateSchema = Joi.object({
+ name: Joi.string().optional().max(100)
+});
/**
* @type {RouteDescription}
@@ -10,12 +14,97 @@ export const getDevicesRoute = {
path: '/auth/devices',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireAuth],
description: 'Get all devices registered to the user',
async method(req, res) {
- const data = await RegisterDto.create(req.body);
- const registerResult = await registerUser(data);
- res.send(registerResult);
+ res.send(req.user.devices);
+ }
+ })
+ }
+};
+
+/**
+ * @type {RouteDescription}
+ */
+export const manageDeviceRoute = {
+ path: '/auth/devices/:id',
+ methods: {
+ get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
+ middlewares: [requireAuth],
+ description: 'Get user device by ID',
+ async method(req, res) {
+ const device = req.user.devices.find(
+ device => device.id === req.params.id
+ );
+ if (!device) {
+ res.status(404).send(
+ new SafeNSoundError({
+ errCode: 'ENTITY_NOT_FOUND',
+ message: 'Device not found'
+ })
+ );
+ return;
+ }
+ res.send(device);
+ }
+ }),
+ delete: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
+ middlewares: [requireAuth],
+ description: 'Delete user device by ID',
+ async method(req, res) {
+ const deviceIndex = req.user.devices.findIndex(
+ device => device.id === req.params.id
+ );
+ if (deviceIndex === -1) {
+ res.status(404).send(
+ new SafeNSoundError({
+ errCode: 'ENTITY_NOT_FOUND',
+ message: 'Device not found'
+ })
+ );
+ return;
+ }
+ req.user.devices.splice(deviceIndex, 1);
+ await req.user.save();
+ res.status(204).send();
+ }
+ }),
+ patch: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
+ exampleBody: {
+ name: 'New Device Name'
+ },
+ middlewares: [requireAuth],
+ description: 'Update user device by ID',
+ async method(req, res) {
+ const device = req.user.devices.find(
+ device => device.id === req.params.id
+ );
+ if (!device) {
+ res.status(404).send(
+ new SafeNSoundError({
+ errCode: 'ENTITY_NOT_FOUND',
+ message: 'Device not found'
+ })
+ );
+ return;
+ }
+ if (req.body.name) {
+ device.name = req.body.name;
+ }
+ await req.user.save();
+ res.send(device);
}
})
}
diff --git a/src/api/routes/auth/index.js b/src/api/routes/auth/index.js
index 2d2cc86..e687911 100644
--- a/src/api/routes/auth/index.js
+++ b/src/api/routes/auth/index.js
@@ -1,3 +1,2 @@
export * from './accountRoutes.js';
export * from './deviceRoutes.js';
-export * from './adminAccountRoutes.js';
diff --git a/src/api/routes/budgetRoutes.js b/src/api/routes/budgetRoutes.js
index bcb9711..e522442 100644
--- a/src/api/routes/budgetRoutes.js
+++ b/src/api/routes/budgetRoutes.js
@@ -3,7 +3,7 @@ import {
requireUser,
requireRole
} from '#api/middlewares/index.js';
-import { UserType } from '#db/schemas/index.js';
+import { DbSpendHistory, UserType } from '#db/schemas/index.js';
import {
RouteDescription,
RouteMethod,
@@ -11,14 +11,25 @@ import {
} from '#api/RouteDescription.js';
import { getUserById } from '#db/dbAccess/index.js';
import { SafeNSoundError } from '#util/error.js';
+import Joi from 'joi';
+
+const budgetModifySchema = new Joi.object({
+ venue: Joi.string().required().max(100),
+ reason: Joi.string().required().max(500),
+ amount: Joi.number().required().min(0),
+ createdAt: Joi.forbidden()
+});
/**
* @type {RouteDescription}
*/
export const getBudgetByUserRoute = {
- path: '/budget/:id',
+ path: '/user/:id/budget',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireMonitor],
description: 'Get the budget for a monitored user',
async method(req, res) {
@@ -33,40 +44,40 @@ export const getBudgetByUserRoute = {
const user = await getUserById(req.params.id);
res.send({ balance: user.balance });
}
- })
- }
-};
-
-/**
- * @type {RouteDescription}
- */
-export const addBudgetByUserRoute = {
- path: '/budget/:id/add',
- methods: {
- get: new RouteMethod({
- description: 'Add budget to a monitored user',
+ }),
+ patch: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
+ exampleBody: {
+ venue: 'Monitor 123',
+ reason: 'Just short for a coke to deal with diabetes',
+ amount: 0.15
+ },
middlewares: [requireMonitor],
+ description: 'Add budget for a monitored user',
async method(req, res) {
- if (req.user.type !== UserType.ADMIN) {
- if (!req.user.monitoredUsers.includes(req.params.id))
- throw new SafeNSoundError({
- errCode: 'UNAUTHORIZED',
- message:
- "You do not have permission to add budget to this user's account."
- });
- }
-
- const user = await getUserById(req.params.id);
- const amount = parseFloat(req.query.amount);
- if (isNaN(amount) || amount <= 0) {
+ if (
+ req.user.type !== UserType.ADMIN &&
+ !req.user.monitoredUsers.includes(req.params.id)
+ )
throw new SafeNSoundError({
- errCode: 'INVALID_AMOUNT',
- message: 'Invalid amount specified.'
+ errCode: 'UNAUTHORIZED',
+ message:
+ "You do not have permission to update this user's budget."
});
- }
- user.balance += amount;
+ let data = await budgetModifySchema.validateAsync(req.body);
+ const user = await getUserById(req.params.id);
+ user.balance += data.amount;
+ let histEntry = await DbSpendHistory.create({
+ venue: data.venue,
+ reason: data.reason,
+ amount: data.amount
+ });
+ user.spendHistory.push(histEntry._id);
await user.save();
+
res.send({ balance: user.balance });
}
})
@@ -76,14 +87,51 @@ export const addBudgetByUserRoute = {
/**
* @type {RouteDescription}
*/
-export const getBudgetRoute = {
+export const userBudgetRoute = {
path: '/budget/@me',
methods: {
get: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
middlewares: [requireUser],
async method(req, res) {
res.send({ currentBalance: req.user.balance });
}
+ }),
+ patch: new RouteMethod({
+ exampleHeaders: {
+ Authorization: 'Bearer {{accessToken}}'
+ },
+ exampleBody: {
+ venue: 'The Store',
+ reason: 'Bought a coke',
+ amount: 0.85
+ },
+ middlewares: [requireUser],
+ description: 'Spend part of budget',
+ async method(req, res) {
+ let data = await budgetModifySchema.validateAsync(req.body);
+
+ if (data.amount > req.user.balance) {
+ throw new SafeNSoundError({
+ errCode: 'INSUFFICIENT_FUNDS',
+ message:
+ 'You do not have enough funds to complete this transaction.'
+ });
+ }
+
+ req.user.balance -= data.amount;
+ let histEntry = await DbSpendHistory.create({
+ venue: data.venue,
+ reason: data.reason,
+ amount: data.amount
+ });
+ req.user.spendHistory.push(histEntry._id);
+ await req.user.save();
+
+ res.send({ balance: req.user.balance });
+ }
})
}
};
|