FIX admin delete image route

This commit is contained in:
Eddi3_As 2025-03-16 20:26:55 -04:00
parent f006dfd195
commit 3124d23df3
10 changed files with 139 additions and 91 deletions

View file

@ -16,7 +16,7 @@
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@mui/icons-material": "^6.4.6", "@mui/icons-material": "^6.4.6",
"@mui/lab": "^5.0.0-alpha.153", "@mui/lab": "^5.0.0-alpha.153",
"@mui/material": "^6.4.6", "@mui/material": "^6.4.7",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"axios": "^1.8.1", "axios": "^1.8.1",
"dompurify": "^3.2.3", "dompurify": "^3.2.3",
@ -3397,9 +3397,9 @@
} }
}, },
"node_modules/@mui/core-downloads-tracker": { "node_modules/@mui/core-downloads-tracker": {
"version": "6.4.6", "version": "6.4.7",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.6.tgz", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.7.tgz",
"integrity": "sha512-rho5Q4IscbrVmK9rCrLTJmjLjfH6m/NcqKr/mchvck0EIXlyYUB9+Z0oVmkt/+Mben43LMRYBH8q/Uzxj/c4Vw==", "integrity": "sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -3474,14 +3474,14 @@
} }
}, },
"node_modules/@mui/material": { "node_modules/@mui/material": {
"version": "6.4.6", "version": "6.4.7",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.6.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.7.tgz",
"integrity": "sha512-6UyAju+DBOdMogfYmLiT3Nu7RgliorimNBny1pN/acOjc+THNFVE7hlxLyn3RDONoZJNDi/8vO4AQQr6dLAXqA==", "integrity": "sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.26.0", "@babel/runtime": "^7.26.0",
"@mui/core-downloads-tracker": "^6.4.6", "@mui/core-downloads-tracker": "^6.4.7",
"@mui/system": "^6.4.6", "@mui/system": "^6.4.7",
"@mui/types": "^7.2.21", "@mui/types": "^7.2.21",
"@mui/utils": "^6.4.6", "@mui/utils": "^6.4.6",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
@ -3502,7 +3502,7 @@
"peerDependencies": { "peerDependencies": {
"@emotion/react": "^11.5.0", "@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0", "@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^6.4.6", "@mui/material-pigment-css": "^6.4.7",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@ -3584,9 +3584,9 @@
} }
}, },
"node_modules/@mui/material/node_modules/@mui/system": { "node_modules/@mui/material/node_modules/@mui/system": {
"version": "6.4.6", "version": "6.4.7",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.6.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.7.tgz",
"integrity": "sha512-FQjWwPec7pMTtB/jw5f9eyLynKFZ6/Ej9vhm5kGdtmts1z5b7Vyn3Rz6kasfYm1j2TfrfGnSXRvvtwVWxjpz6g==", "integrity": "sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.26.0", "@babel/runtime": "^7.26.0",

View file

@ -20,7 +20,7 @@
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@mui/icons-material": "^6.4.6", "@mui/icons-material": "^6.4.6",
"@mui/lab": "^5.0.0-alpha.153", "@mui/lab": "^5.0.0-alpha.153",
"@mui/material": "^6.4.6", "@mui/material": "^6.4.7",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
"axios": "^1.8.1", "axios": "^1.8.1",
"dompurify": "^3.2.3", "dompurify": "^3.2.3",

View file

@ -17,3 +17,8 @@ export interface QuizTypeShort {
created_at: Date; created_at: Date;
updated_at: Date; updated_at: Date;
} }
export interface QuizResponse {
quizzes: QuizTypeShort[];
total: number;
}

View file

@ -10,8 +10,7 @@ import {
Paper, Paper,
Box, Box,
CircularProgress, CircularProgress,
Button, Button
Typography
} from "@mui/material"; } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import { ImageType } from "../../Types/ImageType"; import { ImageType } from "../../Types/ImageType";

View file

@ -1,30 +1,87 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton } from "@mui/material"; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton, Grid, Typography, CircularProgress, Box } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import ApiService from '../../services/ApiService'; import ApiService from '../../services/ApiService';
import { QuizTypeShort } from "../../Types/QuizType"; import { QuizTypeShort } from "../../Types/QuizType";
const Users: React.FC = () => { const Users: React.FC = () => {
const [quizzes, setQuizzes] = useState<QuizTypeShort[]>([]); const [quizzes, setQuizzes] = useState<QuizTypeShort[]>([]);
const [monthlyQuizzes, setMonthlyQuizzes] = useState(0);
const [totalUsers, setTotalUsers] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
const fetchUsers = async () => { const fetchStats = async () => {
try { try {
const data = await ApiService.getQuizzes(); const data = await ApiService.getStats();
setQuizzes(data); setQuizzes(data.quizzes);
setTotalUsers(data.total);
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
const filteredQuizzes = data.quizzes.filter((quiz: QuizTypeShort) => {
const quizDate = new Date(quiz.created_at);
return quizDate.getMonth() === currentMonth && quizDate.getFullYear() === currentYear;
});
setMonthlyQuizzes(filteredQuizzes.length === 0 ? 10 : 0);
} catch (error) { } catch (error) {
console.error("Error fetching quizzes:", error); console.error("Error fetching quizzes:", error);
} finally {
setLoading(false);
} }
}; };
fetchUsers(); fetchStats();
}, []); }, []);
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
setQuizzes(quizzes.filter(quiz => quiz._id !== id)); setQuizzes(quizzes.filter(quiz => quiz._id !== id));
}; };
const totalQuizzes = quizzes.length;
if (loading) {
return ( return (
<TableContainer component={Paper} className="p-4"> <Box display="flex" justifyContent="center" alignItems="center" height="100vh">
<CircularProgress size={80} thickness={5} />
</Box>
);
}
return (
<Paper className="p-4" sx={{ boxShadow: 'none' }}>
<Grid container spacing={8} justifyContent="center">
<Grid item>
<Typography variant="h6" align="center">Quiz du Mois</Typography>
<Box position="relative" display="inline-flex">
<CircularProgress variant="determinate" value={monthlyQuizzes === 0 ? 0 : monthlyQuizzes} size={80} thickness={5} />
<Box position="absolute" top={0} left={0} bottom={0} right={0} display="flex" alignItems="center" justifyContent="center">
<Typography variant="h6">{monthlyQuizzes}</Typography>
</Box>
</Box>
</Grid>
<Grid item>
<Typography variant="h6" align="center">Quiz total</Typography>
<Box position="relative" display="inline-flex">
<CircularProgress variant="determinate" value={totalQuizzes} size={80} thickness={5} />
<Box position="absolute" top={0} left={0} bottom={0} right={0} display="flex" alignItems="center" justifyContent="center">
<Typography variant="h6">{totalQuizzes}</Typography>
</Box>
</Box>
</Grid>
<Grid item>
<Typography variant="h6" align="center">Enseignants</Typography>
<Box position="relative" display="inline-flex">
<CircularProgress variant="determinate" value={totalUsers} size={80} thickness={5} />
<Box position="absolute" top={0} left={0} bottom={0} right={0} display="flex" alignItems="center" justifyContent="center">
<Typography variant="h6">{totalUsers}</Typography>
</Box>
</Box>
</Grid>
</Grid>
{/* Table */}
<TableContainer component={Paper} className="mt-4">
<Table> <Table>
<TableHead> <TableHead>
<TableRow> <TableRow>
@ -51,6 +108,7 @@ const Users: React.FC = () => {
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
</Paper>
); );
}; };

View file

@ -3,7 +3,7 @@ import { jwtDecode } from 'jwt-decode';
import { ENV_VARIABLES } from '../constants'; import { ENV_VARIABLES } from '../constants';
import { FolderType } from 'src/Types/FolderType'; import { FolderType } from 'src/Types/FolderType';
import { QuizType, QuizTypeShort } from 'src/Types/QuizType'; import { QuizType, QuizResponse } from 'src/Types/QuizType';
import { RoomType } from 'src/Types/RoomType'; import { RoomType } from 'src/Types/RoomType';
import { UserType } from 'src/Types/UserType'; import { UserType } from 'src/Types/UserType';
import { ImagesResponse, ImagesParams } from 'src/Types/ImageType'; import { ImagesResponse, ImagesParams } from 'src/Types/ImageType';
@ -1181,7 +1181,6 @@ public async login(email: string, password: string): Promise<any> {
if (result.status !== 200) { if (result.status !== 200) {
throw new Error(`L'obtention des titres des salles a échoué. Status: ${result.status}`); throw new Error(`L'obtention des titres des salles a échoué. Status: ${result.status}`);
} }
console.log(result.data);
return result.data.users; return result.data.users;
} catch (error) { } catch (error) {
@ -1209,7 +1208,6 @@ public async login(email: string, password: string): Promise<any> {
if (result.status !== 200) { if (result.status !== 200) {
throw new Error(`L'affichage des images a échoué. Status: ${result.status}`); throw new Error(`L'affichage des images a échoué. Status: ${result.status}`);
} }
console.log(result.data);
const images = result.data.data; const images = result.data.data;
return images; return images;
@ -1257,18 +1255,18 @@ public async login(email: string, password: string): Promise<any> {
} }
} }
public async getQuizzes(): Promise<QuizTypeShort[]> { public async getStats(): Promise<QuizResponse> {
try { try {
const url: string = this.constructRequestUrl(`/admin/getQuizzes`); const url: string = this.constructRequestUrl(`/admin/getStats`);
const headers = this.constructRequestHeaders(); const headers = this.constructRequestHeaders();
const result: AxiosResponse = await axios.get(url, { headers }); const result: AxiosResponse = await axios.get(url, { headers });
if (result.status !== 200) { if (result.status !== 200) {
throw new Error(`L'affichage des images a échoué. Status: ${result.status}`); throw new Error(`L'affichage des images a échoué. Status: ${result.status}`);
} }
const quiz = result.data.quizzes; const resp = result.data.data;
return quiz; return resp;
} catch (error) { } catch (error) {
console.log("Error details: ", error); console.log("Error details: ", error);

View file

@ -19,11 +19,11 @@ class AdminController {
} }
}; };
getQuizzes = async (req, res, next) => { getStats = async (req, res, next) => {
try { try {
const quizzes = await this.model.getQuizzes(); const data = await this.model.getStats();
return res.status(200).json({ quizzes }); return res.status(200).json({ data });
} catch (error) { } catch (error) {
return next(error); return next(error);
} }
@ -62,41 +62,20 @@ class AdminController {
} }
}; };
deleteQuiz = async (req, res, next) => {
try {
const { id } = req.params;
if (!id) {
throw new AppError(MISSING_REQUIRED_PARAMETER);
}
const user = await this.model.deleteUser(id);
if (!user) {
throw new AppError(IMAGE_NOT_FOUND);
}
return res.status(200).json({ user: user });
} catch (error) {
return next(error);
}
};
deleteImage = async (req, res, next) => { deleteImage = async (req, res, next) => {
try { try {
const { id } = req.params; const { imgId } = req.query;
if (!imgId) {
if (!id) {
throw new AppError(MISSING_REQUIRED_PARAMETER); throw new AppError(MISSING_REQUIRED_PARAMETER);
} }
const user = await this.model.deleteUser(id); const deleted = await this.model.deleteImage(imgId);
if (!user) { if (!deleted) {
throw new AppError(IMAGE_NOT_FOUND); throw new AppError(IMAGE_NOT_FOUND);
} }
return res.status(200).json({ user: user }); return res.status(200).json({ deleted });
} catch (error) { } catch (error) {
return next(error); return next(error);
} }

View file

@ -33,17 +33,25 @@ class Admin {
return deleted; return deleted;
} }
async getQuizzes() { async getStats() {
await this.db.connect() await this.db.connect()
const conn = this.db.getConnection(); const conn = this.db.getConnection();
const usrColl = conn.collection('users');
const total = await usrColl.countDocuments();
const quizColl = conn.collection('files'); const quizColl = conn.collection('files');
const projection = { content: 0, folderName: 0, folderId: 0 }; const projection = { content: 0, folderName: 0, folderId: 0 };
const result = await quizColl.find({}, projection).toArray(); const result = await quizColl.find({}, projection).toArray();
if (!result) return null; if (!result) return null;
return result; let respObj = {
quizzes: result,
total: total
}
return respObj;
} }
async getImages(page, limit) { async getImages(page, limit) {
@ -78,14 +86,14 @@ class Admin {
return respObj; return respObj;
} }
async deleteImage(uid, imgId) { async deleteImage(imgId) {
let resp = false; let resp = false;
await this.db.connect() await this.db.connect()
const conn = this.db.getConnection(); const conn = this.db.getConnection();
const quizColl = conn.collection('files'); const quizColl = conn.collection('files');
const rgxImg = new RegExp(`/api/image/get/${imgId}`); const rgxImg = new RegExp(`/api/image/get/${imgId}`);
const result = await quizColl.find({ userId: uid, content: { $regex: rgxImg }}).toArray(); const result = await quizColl.find({ content: { $regex: rgxImg }}).toArray();
if(!result || result.length < 1){ if(!result || result.length < 1){
const imgsColl = conn.collection('images'); const imgsColl = conn.collection('images');
const isDeleted = await imgsColl.deleteOne({ _id: ObjectId.createFromHexString(imgId) }); const isDeleted = await imgsColl.deleteOne({ _id: ObjectId.createFromHexString(imgId) });

View file

@ -70,6 +70,7 @@ class Quiz {
return true; return true;
} }
async deleteQuizzesByFolderId(folderId) { async deleteQuizzesByFolderId(folderId) {
await this.db.connect(); await this.db.connect();
const conn = this.db.getConnection(); const conn = this.db.getConnection();

View file

@ -6,10 +6,10 @@ const asyncHandler = require('./routerUtils.js');
const jwt = require('../middleware/jwtToken.js'); const jwt = require('../middleware/jwtToken.js');
router.get("/getUsers", asyncHandler(admin.getUsers)); router.get("/getUsers", jwt.authenticate, asyncHandler(admin.getUsers));
router.get("/getQuizzes", asyncHandler(admin.getQuizzes)); router.get("/getStats", jwt.authenticate, asyncHandler(admin.getStats));
router.get("/getImages", asyncHandler(admin.getImages)); router.get("/getImages", jwt.authenticate, asyncHandler(admin.getImages));
router.delete("/deleteUser", asyncHandler(admin.deleteUser)); router.delete("/deleteUser", jwt.authenticate, asyncHandler(admin.deleteUser));
router.delete("/deleteImage", jwt.authenticate, asyncHandler(admin.deleteImage)); router.delete("/deleteImage", jwt.authenticate, asyncHandler(admin.deleteImage));
module.exports = router; module.exports = router;