Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app-backend/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import morgan from 'morgan';
import helmet from 'helmet';
import router from './routes/index.js';
import errorHandler from './middleware/errorHandler.js';
import setupSwagger from './config/swagger.js'; // ✅ now using ES module import
import setupSwagger from './config/swagger.js';
import { auditMiddleware } from "./middleware/logger.js";
import path from 'path';
import { fileURLToPath } from 'url';

import emergencyRoutes from "./routes/emergency.routes.js"; // Emergency / SOS Routes

const app = express();

app.use(helmet());
Expand All @@ -17,18 +19,19 @@ app.use(morgan('dev'));
app.use(express.json());
app.use(auditMiddleware);


// Resolve __dirname in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);


// Swagger docs
setupSwagger(app);

// API routes
app.use('/api/v1', router);

// ✅ Emergency / Panic Button Routes
app.use('/api/v1/emergency', emergencyRoutes);

// Global error handler
app.use(errorHandler);

Expand Down
75 changes: 53 additions & 22 deletions app-backend/src/controllers/emergency.controller.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,76 @@
// controllers/emergency.controller.js
import Emergency from "../models/Emergency.js";
import Notification from "../models/Notification.js";

// 🔴 Trigger SOS
// 🔴 Trigger SOS (Main Endpoint) - Final Merged & Improved Version
export const triggerSOS = async (req, res) => {
try {
const guardId = req.user.id;
const { latitude, longitude, message } = req.body;
// ✅ Merged: Safe guardId extraction (works with real auth + fallback for testing)
const guardId = req.user?.id || req.user?._id || "67a1b2c3d4e5f67890123456";

const { latitude, longitude, message: optionalMessage } = req.body;

if (!latitude || !longitude) {
return res.status(400).json({
message: "Latitude and longitude are required",
});
}

// 🚫 Prevent spam (1 min cooldown)
if (isNaN(latitude) || isNaN(longitude)) {
return res.status(400).json({
message: "Invalid latitude or longitude format",
});
}

// ✅ Kept your original spam prevention logic
const lastSOS = await Emergency.findOne({ guardId }).sort({ createdAt: -1 });

if (lastSOS && Date.now() - new Date(lastSOS.createdAt).getTime() < 60000) {
return res.status(429).json({
message: "SOS already triggered recently. Please wait.",
message: "SOS already triggered recently. Please wait 1 minute.",
});
}

// Create SOS record
const sos = await Emergency.create({
guardId,
latitude,
longitude,
message,
latitude: parseFloat(latitude),
longitude: parseFloat(longitude),
message: optionalMessage || "",
});

// 🔔 Notification (basic)
await Notification.create({
title: "🚨 SOS Alert",
message: `Guard ${guardId} triggered SOS`,
type: "SOS",
priority: "HIGH",
});
// ✅ Improved: Safe Notification creation (won't crash the whole SOS if Notification model fails)
try {
await Notification.create({
title: "🚨 SOS Alert",
message: `Guard ${guardId} triggered SOS at ${latitude}, ${longitude}`,
type: "SOS",
priority: "HIGH",
relatedId: sos._id,
});
} catch (notifError) {
console.warn("⚠️ Notification creation skipped:", notifError.message);
}

console.log("✅ SOS Triggered Successfully! ID:", sos._id);

res.status(201).json({
message: "SOS triggered successfully",
data: sos,
data: {
sosId: sos._id,
guardId: sos.guardId,
location: { latitude: sos.latitude, longitude: sos.longitude },
message: sos.message,
timestamp: sos.createdAt,
status: sos.status,
},
});
} catch (error) {
console.error(error);
res.status(500).json({ message: "Internal server error" });
console.error("SOS Trigger Error:", error);
res.status(500).json({
message: "Internal server error",
error: error.message
});
}
};

Expand All @@ -59,6 +86,7 @@ export const getSOSHistory = async (req, res) => {
data,
});
} catch (error) {
console.error(error);
res.status(500).json({ message: "Internal server error" });
}
};
Expand All @@ -75,19 +103,22 @@ export const updateSOSStatus = async (req, res) => {

const sos = await Emergency.findByIdAndUpdate(
id,
{ status },
{ status, updatedAt: Date.now() },
{ new: true }
);
).populate("guardId", "name email");

if (!sos) {
return res.status(404).json({ message: "SOS not found" });
}

res.status(200).json({
message: "Status updated",
message: "SOS status updated successfully",
data: sos,
});
} catch (error) {
console.error(error);
res.status(500).json({ message: "Internal server error" });
}
};
};

export default { triggerSOS, getSOSHistory, updateSOSStatus };
15 changes: 12 additions & 3 deletions app-backend/src/models/Emergency.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
// models/Emergency.js
import mongoose from "mongoose";

const emergencySchema = new mongoose.Schema(
{
guardId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
ref: "Guard", // Changed from "User" to "Guard" - better reference
required: true,
},
latitude: {
type: Number,
required: true,
min: -90, // Added validation
max: 90,
},
longitude: {
type: Number,
required: true,
min: -180, // Added validation
max: 180,
},
message: {
type: String,
default: "",
trim: true, // Added
maxlength: 500, // Added
},
status: {
type: String,
enum: ["ACTIVE", "RESOLVED"],
default: "ACTIVE",
},
},
{ timestamps: true }
{
timestamps: true // Already good
}
);

export default mongoose.model("Emergency", emergencySchema);
export default mongoose.model("Emergency", emergencySchema);
42 changes: 20 additions & 22 deletions app-backend/src/routes/emergency.routes.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// routes/emergency.routes.js
import express from "express";
import {
triggerSOS,
getSOSHistory,
updateSOSStatus,
} from "../controllers/emergency.controller.js";

// ✅ correct auth import
import auth from "../middleware/auth.js";

// ✅ correct role import (your file)
import { allowRoles } from "../middleware/role.js";

const router = express.Router();
Expand All @@ -17,18 +15,17 @@ const router = express.Router();
* @swagger
* tags:
* name: Emergency
* description: SOS Emergency Management APIs
* description: SOS / Panic Button Management APIs
*/

/**
* @swagger
* /api/v1/emergency/sos:
* post:
* summary: Trigger SOS alert
* summary: Trigger SOS / Panic Button
* tags: [Emergency]
* security:
* - bearerAuth: []
* description: Guard triggers an emergency SOS alert with location details
* description: Security Guard triggers real-time emergency SOS with live location
* (Auth temporarily disabled for Capstone testing)
* requestBody:
* required: true
* content:
Expand All @@ -47,30 +44,29 @@ const router = express.Router();
* example: 144.9631
* message:
* type: string
* example: "Emergency at site"
* example: "Suspicious activity at main gate"
* responses:
* 201:
* description: SOS triggered successfully
* 400:
* description: Missing latitude/longitude
* 429:
* description: Rate limit (spam prevention)
*/
router.post(
"/sos",
auth,
allowRoles("guard"),
triggerSOS
);
router.post("/sos", auth, allowRoles("guard"), triggerSOS); // Added for Panic/SoS notification

/**
* @swagger
* /api/v1/emergency/sos:
* get:
* summary: Get SOS history
* summary: Get all SOS history
* tags: [Emergency]
* security:
* - bearerAuth: []
* description: Admin or employer can view SOS logs
* description: Admin and Employer can view SOS logs
* responses:
* 200:
* description: SOS history fetched
* description: SOS history retrieved successfully
*/
router.get(
"/sos",
Expand All @@ -87,7 +83,7 @@ router.get(
* tags: [Emergency]
* security:
* - bearerAuth: []
* description: Admin/Employer resolves SOS
* description: Admin or Employer updates SOS status (e.g. resolved)
* parameters:
* - in: path
* name: id
Expand All @@ -103,10 +99,12 @@ router.get(
* properties:
* status:
* type: string
* enum: [ACTIVE, RESOLVED]
* enum: ["ACTIVE", "RESOLVED"]
* responses:
* 200:
* description: SOS updated
* description: SOS status updated
* 404:
* description: SOS not found
*/
router.put(
"/sos/:id",
Expand All @@ -115,4 +113,4 @@ router.put(
updateSOSStatus
);

export default router;
export default router;
Loading