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

witseie-elen4010 / 2026-group-lab-002 / 25973956313

16 May 2026 09:55PM UTC coverage: 89.978%. Remained the same
25973956313

push

github

Sarcastic-Sunflower
Merge branch 'main' of github.com:witseie-elen4010/2026-group-lab-002

666 of 794 branches covered (83.88%)

Branch coverage included in aggregate %.

48 of 65 new or added lines in 15 files covered. (73.85%)

38 existing lines in 9 files now uncovered.

1417 of 1521 relevant lines covered (93.16%)

13.52 hits per line

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

93.88
/src/controllers/availability-controller.js
1
const db = require('../../database/db')
13✔
2
const { logActivity } = require('../services/logging-service')
13✔
3
const ActionTypes = require('../services/action-types')
13✔
4

5
const { validateSlotFields, isBusinessHours, isOverlapping, isMaxBookingValid, computeDuration } = require('../services/availability-helpers')
13✔
6
const { validateRequiredText } = require('../services/input-validation')
13✔
7

8
const getAvailability = (staffNumber) =>
13✔
9
  db.prepare(`
18✔
10
    SELECT * FROM lecturer_availability
11
    WHERE staff_number = ?
12
    ORDER BY
13
      CASE day_of_week WHEN 'Mon' THEN 1 WHEN 'Tue' THEN 2 WHEN 'Wed' THEN 3 WHEN 'Thu' THEN 4 WHEN 'Fri' THEN 5 END,
14
      start_time
15
  `).all(staffNumber)
16

17
const showAvailability = (req, res) => {
13✔
18
  if (!req.session || !req.session.userId) {
2!
NEW
19
    return res.redirect('/login');
×
20
  }
21
  const user = { id: req.session.userId, name: req.session.userName, role: req.session.userRole }
2✔
22
  res.render('availability', { user, availability: getAvailability(user.id), error: null, success: null })
2✔
23
}
24

25
const saveAvailability = async (req, res) => {
13✔
26
  const user = { id: req.session.userId, name: req.session.userName, role: req.session.userRole }
7✔
27
  const { day_of_week, start_time, end_time, venue, max_number_of_students, max_booking_min } = req.body
7✔
28

29
  if (!validateSlotFields({ day_of_week, start_time, end_time, venue, max_number_of_students, max_booking_min })) {
7✔
30
    return res.render('availability', {
1✔
31
      user,
32
      availability: getAvailability(user.id),
33
      error: 'All fields are required.',
34
      success: null
35
    })
36
  }
37

38
  const venueError = validateRequiredText('Venue', venue, 100)
6✔
39
  if (venueError) {
6!
UNCOV
40
    return res.render('availability', {
×
41
      user,
42
      availability: getAvailability(user.id),
43
      error: venueError,
44
      success: null
45
    })
46
  }
47

48
  if (Number(max_number_of_students) < 1 || Number(max_number_of_students) > 10) {
6✔
49
    return res.render('availability', {
1✔
50
      user,
51
      availability: getAvailability(user.id),
52
      error: 'Max students must be between 1 and 10.',
53
      success: null
54
    })
55
  }
56

57
  if (!isBusinessHours(start_time, end_time)) {
5✔
58
    return res.render('availability', {
1✔
59
      user,
60
      availability: getAvailability(user.id),
61
      error: 'Slots must be between 08:00 and 18:00, with end time after start time.',
62
      success: null
63
    })
64
  }
65

66
  if (!isMaxBookingValid(start_time, end_time, max_booking_min)) {
4✔
67
    const windowLength = computeDuration(start_time, end_time)
1✔
68
    return res.render('availability', {
1✔
69
      user,
70
      availability: getAvailability(user.id),
71
      error: `Max consultation duration (${Number(max_booking_min)} min) cannot exceed window length (${windowLength} min).`,
72
      success: null
73
    })
74
  }
75

76
  const existing = db.prepare(
3✔
77
    'SELECT * FROM lecturer_availability WHERE staff_number = ? AND day_of_week = ?'
78
  ).all(user.id, day_of_week)
79

80
  if (isOverlapping(existing, start_time, end_time)) {
3✔
81
    return res.render('availability', {
1✔
82
      user,
83
      availability: getAvailability(user.id),
84
      error: 'This slot overlaps with an existing availability slot.',
85
      success: null
86
    })
87
  }
88

89
  const lastRecord = db.prepare(
2✔
90
    'INSERT INTO lecturer_availability (staff_number, day_of_week, start_time, end_time, venue, max_number_of_students, max_booking_min) VALUES (?, ?, ?, ?, ?, ?, ?)'
91
  ).run(user.id, day_of_week, start_time, end_time, venue, Number(max_number_of_students), Number(max_booking_min))
92

93
  await logActivity(req.session.userId, ActionTypes.AVAIL_CREATE, [{ table: 'lecturer_availability', id: lastRecord.lastInsertRowid }])
2✔
94
  return res.render('availability', {
2✔
95
    user,
96
    availability: getAvailability(user.id),
97
    error: null,
98
    success: 'Availability slot saved.'
99
  })
100
}
101

102
const deleteAvailability = async (req, res) => {
13✔
103
  const user = { id: req.session.userId, name: req.session.userName, role: req.session.userRole }
1✔
104
  db.prepare(
1✔
105
    'DELETE FROM lecturer_availability WHERE availability_id = ? AND staff_number = ?'
106
  ).run(req.params.id, user.id)
107

108
  await logActivity(req.session.userId, ActionTypes.AVAIL_CANCEL, [{ table: 'lecturer_availability', id: req.params.id }])
1✔
109
  return res.render('availability', {
1✔
110
    user,
111
    availability: getAvailability(user.id),
112
    error: null,
113
    success: 'Slot deleted.'
114
  })
115
}
116

117
const updateAvailability = async (req, res) => {
13✔
118
  const user = { id: req.session.userId, name: req.session.userName, role: req.session.userRole }
8✔
119
  const { id } = req.params
8✔
120
  const { day_of_week, start_time, end_time, venue, max_number_of_students, max_booking_min } = req.body
8✔
121

122
  if (!validateSlotFields({ day_of_week, start_time, end_time, venue, max_number_of_students, max_booking_min })) {
8✔
123
    return res.render('availability', {
1✔
124
      user, availability: getAvailability(user.id),
125
      error: 'All fields are required.', success: null
126
    })
127
  }
128

129
  const venueError = validateRequiredText('Venue', venue, 100)
7✔
130
  if (venueError) {
7!
UNCOV
131
    return res.render('availability', {
×
132
      user, availability: getAvailability(user.id),
133
      error: venueError, success: null
134
    })
135
  }
136

137
  if (Number(max_number_of_students) < 1 || Number(max_number_of_students) > 10) {
7✔
138
    return res.render('availability', {
1✔
139
      user, availability: getAvailability(user.id),
140
      error: 'Max students must be between 1 and 10.', success: null
141
    })
142
  }
143

144
  if (!isBusinessHours(start_time, end_time)) {
6✔
145
    return res.render('availability', {
1✔
146
      user, availability: getAvailability(user.id),
147
      error: 'Slots must be between 08:00 and 18:00, with end time after start time.', success: null
148
    })
149
  }
150

151
  if (!isMaxBookingValid(start_time, end_time, max_booking_min)) {
5✔
152
    const windowLength = computeDuration(start_time, end_time)
1✔
153
    return res.render('availability', {
1✔
154
      user, availability: getAvailability(user.id),
155
      error: `Max consultation duration (${Number(max_booking_min)} min) cannot exceed window length (${windowLength} min).`,
156
      success: null
157
    })
158
  }
159

160
  const slot = db.prepare(
4✔
161
    'SELECT * FROM lecturer_availability WHERE availability_id = ? AND staff_number = ?'
162
  ).get(id, user.id)
163

164
  if (!slot) {
4✔
165
    return res.render('availability', {
1✔
166
      user, availability: getAvailability(user.id),
167
      error: 'Slot not found.', success: null
168
    })
169
  }
170

171
  const otherSlots = db.prepare(
3✔
172
    'SELECT * FROM lecturer_availability WHERE staff_number = ? AND day_of_week = ? AND availability_id != ?'
173
  ).all(user.id, day_of_week, id)
174

175
  if (isOverlapping(otherSlots, start_time, end_time)) {
3✔
176
    return res.render('availability', {
1✔
177
      user, availability: getAvailability(user.id),
178
      error: 'This slot overlaps with an existing availability slot.', success: null
179
    })
180
  }
181

182
  db.prepare(
2✔
183
    'UPDATE lecturer_availability SET day_of_week = ?, start_time = ?, end_time = ?, venue = ?, max_number_of_students = ?, max_booking_min = ? WHERE availability_id = ? AND staff_number = ?'
184
  ).run(day_of_week, start_time, end_time, venue, Number(max_number_of_students), Number(max_booking_min), id, user.id)
185

186
  await logActivity(req.session.userId, ActionTypes.AVAIL_UPDATE, [{ table: 'lecturer_availability', id }])
2✔
187
  return res.render('availability', {
2✔
188
    user, availability: getAvailability(user.id),
189
    error: null, success: 'Availability slot updated.'
190
  })
191
}
192

193
module.exports = { showAvailability, saveAvailability, deleteAvailability, updateAvailability }
13✔
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