• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Seniru / defendxstore / 14788669982

02 May 2025 04:26AM UTC coverage: 41.245% (-0.3%) from 41.521%
14788669982

push

github

web-flow
feat: send email for stock alerts (#80)

* template for the inventory alert

* feat: add out of stock email alert for admins

* prettify applied

* fix: improve wording in stock alert email template

* revision update

* revert to main(email.js)

100 of 420 branches covered (23.81%)

Branch coverage included in aggregate %.

5 of 14 new or added lines in 2 files covered. (35.71%)

25 existing lines in 2 files now uncovered.

569 of 1202 relevant lines covered (47.34%)

8.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.95
/backend/src/controllers/items.js
1
const mongoose = require("mongoose")
3✔
2
const Item = require("../models/Item")
3✔
3
const User = require("../models/User")
3✔
4
const createResponse = require("../utils/createResponse")
3✔
5
const { StatusCodes } = require("http-status-codes")
3✔
6
const { sendMail } = require("../services/email")
3✔
7
const { roles } = require("../utils/getRoles")
3✔
8
require("dotenv").config()
3✔
9

10
// Get All Items
11
const getAllItems = async (req, res) => {
3✔
12
    try {
×
13
        const items = await Item.find()
×
14
        createResponse(res, StatusCodes.OK, items)
×
15
    } catch (error) {
16
        next(error)
×
17
    }
18
}
19

20
// Get Item by ID
21
const getItemById = async (req, res) => {
3✔
22
    const { id } = req.params
×
23
    try {
×
24
        if (!mongoose.Types.ObjectId.isValid(id)) {
×
25
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid id for item")
×
26
        }
27
        const item = await Item.findById(id)
×
28
        if (!item) {
×
29
            return createResponse(res, StatusCodes.NOT_FOUND, "Item not found")
×
30
        }
31
        return createResponse(res, StatusCodes.OK, item)
×
32
    } catch (error) {
33
        next(error)
×
34
    }
35
}
36

37
const getTrendingItems = async (req, res, next) => {
3✔
38
    try {
×
39
        const response = await fetch(`${process.env.AI_SERVICES_URI}/trending/items`)
×
40
        if (!response.ok)
×
41
            return createResponse(res, response.status, response.body || response.statusText)
×
42
        const result = await response.json()
×
43
        const items = await Item.find({ _id: { $in: result.map((item) => item[0]) } })
×
44
            .limit(8)
45
            .exec()
46
        return createResponse(res, StatusCodes.OK, items)
×
47
    } catch (error) {
48
        next(error)
×
49
    }
50
}
51

52
const getRecommendedItems = async (req, res, next) => {
3✔
53
    try {
×
54
        const user = await User.findOne({ username: req.user.username }).exec()
×
55
        const response = await fetch(
×
56
            `${process.env.AI_SERVICES_URI}/recommendations/items?user_id=${user._id}`,
57
        )
58
        if (!response.ok)
×
59
            return createResponse(res, response.status, response.body || response.statusText)
×
60
        const result = await response.json()
×
61
        const items = await Item.find({ _id: { $in: result } })
×
62
            .limit(8)
63
            .exec()
64
        return createResponse(res, StatusCodes.OK, items)
×
65
    } catch (error) {
66
        next(error)
×
67
    }
68
}
69

70
// Create Item
71
const createItem = async (req, res) => {
3✔
72
    const item = req.body
×
73
    item.size = item.size.split(",")
×
74
    const newItem = new Item(item)
×
75
    try {
×
76
        await newItem.save()
×
77
        return createResponse(res, StatusCodes.CREATED, newItem)
×
78
    } catch (error) {
79
        next(error)
×
80
    }
81
}
82

83
// Update Item
84
const updateItem = async (req, res, next) => {
3✔
85
    const { id } = req.params
×
86
    const item = req.body
×
87
    try {
×
88
        if (!mongoose.Types.ObjectId.isValid(id)) {
×
89
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid id for item")
×
90
        }
91

NEW
92
        const oldItem = await Item.findById(id)
×
NEW
93
        if (!oldItem) {
×
UNCOV
94
            return createResponse(res, StatusCodes.NOT_FOUND, "Item not found")
×
95
        }
96

NEW
97
        const updatedItem = await Item.findByIdAndUpdate(id, item, { new: true })
×
98

NEW
99
        if (oldItem.stock !== "Out of Stock" && updatedItem.stock === "Out of Stock") {
×
100
            // Find all admin users
NEW
101
            const adminUsers = await User.find({
×
102
                role: { $bitsAllSet: roles.ADMIN },
103
            })
104

NEW
105
            if (adminUsers && adminUsers.length > 0) {
×
106
                //  table row for the email template
NEW
107
                const itemRow = `
×
108
                    <tr>
109
                        <td style="padding: 12px; border: 1px solid #ddd;">${updatedItem.itemName}</td>
110
                        <td style="padding: 12px; border: 1px solid #ddd;">${updatedItem.category}</td>
111
                        <td style="padding: 12px; border: 1px solid #ddd; text-align: center;">${updatedItem.quantity}</td>
112
                        <td style="padding: 12px; border: 1px solid #ddd; text-align: center; color: #ff0000; font-weight: bold;">Out of Stock</td>
113
                    </tr>
114
                `
115

116
                // Send email to all admin users
NEW
117
                for (const admin of adminUsers) {
×
NEW
118
                    sendMail(admin.email, "URGENT: Item Out of Stock Alert", "stock_alert", {
×
119
                        title: "Out of Stock Alert",
120
                        itemCount: "1",
121
                        items: itemRow,
122
                        date: new Date().toLocaleDateString(),
123
                    })
124
                }
125
            }
126
        }
127

UNCOV
128
        return createResponse(res, StatusCodes.OK, updatedItem)
×
129
    } catch (error) {
130
        next(error)
×
131
    }
132
}
133

134
// Delete Item
135
const deleteItem = async (req, res) => {
3✔
136
    const { id } = req.params
×
137
    try {
×
138
        if (!mongoose.Types.ObjectId.isValid(id)) {
×
139
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid id for item")
×
140
        }
141
        const deletedItem = await Item.findByIdAndDelete(id)
×
142
        if (!deletedItem) {
×
143
            return createResponse(res, StatusCodes.NOT_FOUND, "Item not found")
×
144
        }
145
        return createResponse(res, StatusCodes.OK, "Item deleted")
×
146
    } catch (error) {
147
        next(error)
×
148
    }
149
}
150

151
module.exports = {
3✔
152
    getAllItems,
153
    getItemById,
154
    getTrendingItems,
155
    getRecommendedItems,
156
    createItem,
157
    updateItem,
158
    deleteItem,
159
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc