Skip to content

Commit ad031cc

Browse files
committed
refactor: route validation errors through AppError and central error handler
1 parent 3b5fef2 commit ad031cc

6 files changed

Lines changed: 34 additions & 21 deletions

File tree

backend/src/middlewares/validateBody.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1+
import { AppError } from '../core/errors.js';
2+
13
export const validateBody = (schema) => {
24
return (req, res, next) => {
35
// If there is no body and the schema expects an object, Joi will handle it (or we can pass {} if req.body is undefined, but express.json() gives {} usually).
46
// It's safer to pass req.body or {}
57
const payload = req.body || {};
68
const { error } = schema.validate(payload);
79
if (error) {
8-
return res.status(400).json({
9-
error: 'VALIDATION_ERROR',
10-
message: error.details[0].message
11-
});
10+
return next(new AppError(error.details[0].message, 400, 'VALIDATION_ERROR'));
1211
}
1312
next();
1413
};

frontend/src/pages/CourseDetail.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ const CourseDetail = () => {
4040

4141
const handleCreate = async (e) => {
4242
e.preventDefault();
43-
await dispatch(createAssignment({ courseId: id, assignmentData: newAssignment }));
43+
let formattedDate = newAssignment.due_date;
44+
if (formattedDate && !formattedDate.includes('T')) {
45+
formattedDate = `${formattedDate}T23:59:59.999Z`;
46+
}
47+
await dispatch(createAssignment({ courseId: id, assignmentData: { ...newAssignment, due_date: formattedDate } }));
4448
setShowCreateModal(false);
4549
setNewAssignment({ title: '', description: '', due_date: '' });
4650
};

frontend/src/services/api.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const API_URL = import.meta.env.VITE_API_URL || '/api';
44

55
const api = axios.create({
66
baseURL: API_URL,
7+
timeout: 30000,
78
headers: {
89
'Content-Type': 'application/json',
910
},
@@ -27,12 +28,21 @@ api.interceptors.request.use(
2728
api.interceptors.response.use(
2829
(response) => response,
2930
(error) => {
30-
if (error.response && error.response.status === 401) {
31-
// Token expired or invalid
32-
localStorage.removeItem('token');
33-
localStorage.removeItem('user');
34-
// Optional: Redirect to login
35-
window.location.href = '/login';
31+
if (error.response) {
32+
if (error.response.status === 401) {
33+
// Token expired or invalid
34+
localStorage.removeItem('token');
35+
localStorage.removeItem('user');
36+
// Optional: Redirect to login
37+
window.location.href = '/login';
38+
}
39+
40+
const apiError = error.response.data?.error?.message
41+
|| error.response.data?.message
42+
|| 'An unexpected error occurred';
43+
error.message = apiError;
44+
} else {
45+
error.message = 'Network error or server unreachable';
3646
}
3747
return Promise.reject(error);
3848
}

frontend/src/store/assignmentSlice.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const fetchAssignments = createAsyncThunk(
88
try {
99
return await courseService.getCourseAssignments(courseId);
1010
} catch (error) {
11-
return rejectWithValue(error.response?.data?.error || 'Failed to fetch assignments');
11+
return rejectWithValue(error.message || 'Failed to fetch assignments');
1212
}
1313
}
1414
);
@@ -20,7 +20,7 @@ export const createAssignment = createAsyncThunk(
2020
const response = await api.post(`/courses/${courseId}/assignments`, assignmentData);
2121
return response.data;
2222
} catch (error) {
23-
return rejectWithValue(error.response?.data?.error || 'Failed to create assignment');
23+
return rejectWithValue(error.message || 'Failed to create assignment');
2424
}
2525
}
2626
);
@@ -32,7 +32,7 @@ export const submitAssignment = createAsyncThunk(
3232
const response = await api.post(`/assignments/${assignmentId}/submit`, { content });
3333
return response.data;
3434
} catch (error) {
35-
return rejectWithValue(error.response?.data?.error || 'Failed to submit assignment');
35+
return rejectWithValue(error.message || 'Failed to submit assignment');
3636
}
3737
}
3838
);
@@ -44,7 +44,7 @@ export const fetchSubmissions = createAsyncThunk(
4444
const response = await api.get(`/assignments/${assignmentId}/submissions`);
4545
return { assignmentId, submissions: response.data };
4646
} catch (error) {
47-
return rejectWithValue(error.response?.data?.error || 'Failed to fetch submissions');
47+
return rejectWithValue(error.message || 'Failed to fetch submissions');
4848
}
4949
}
5050
);

frontend/src/store/authSlice.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const loginUser = createAsyncThunk(
99
const response = await api.post('/auth/login', credentials);
1010
return response.data;
1111
} catch (error) {
12-
return rejectWithValue(error.response?.data?.error || 'Login failed');
12+
return rejectWithValue(error.message || 'Login failed');
1313
}
1414
}
1515
);
@@ -21,7 +21,7 @@ export const registerUser = createAsyncThunk(
2121
const response = await api.post('/auth/register', userData);
2222
return response.data;
2323
} catch (error) {
24-
return rejectWithValue(error.response?.data?.error || 'Registration failed');
24+
return rejectWithValue(error.message || 'Registration failed');
2525
}
2626
}
2727
);

frontend/src/store/courseSlice.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const fetchCourses = createAsyncThunk(
77
try {
88
return await courseService.getAllCourses();
99
} catch (error) {
10-
return rejectWithValue(error.response?.data?.error || 'Failed to fetch courses');
10+
return rejectWithValue(error.message || 'Failed to fetch courses');
1111
}
1212
}
1313
);
@@ -18,7 +18,7 @@ export const fetchEnrolledCourses = createAsyncThunk(
1818
try {
1919
return await courseService.getEnrolledCourses();
2020
} catch (error) {
21-
return rejectWithValue(error.response?.data?.error || 'Failed to fetch enrolled courses');
21+
return rejectWithValue(error.message || 'Failed to fetch enrolled courses');
2222
}
2323
}
2424
);
@@ -29,7 +29,7 @@ export const createNewCourse = createAsyncThunk(
2929
try {
3030
return await courseService.createCourse(courseData);
3131
} catch (error) {
32-
return rejectWithValue(error.response?.data?.error || 'Failed to create course');
32+
return rejectWithValue(error.message || 'Failed to create course');
3333
}
3434
}
3535
);
@@ -40,7 +40,7 @@ export const enrollInCourse = createAsyncThunk(
4040
try {
4141
return await courseService.enrollStudent(courseId);
4242
} catch (error) {
43-
return rejectWithValue(error.response?.data?.error || 'Failed to enroll');
43+
return rejectWithValue(error.message || 'Failed to enroll');
4444
}
4545
}
4646
);

0 commit comments

Comments
 (0)