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

View file

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

View file

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

View file

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

View file

@ -1,30 +1,87 @@
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 ApiService from '../../services/ApiService';
import { QuizTypeShort } from "../../Types/QuizType";
const Users: React.FC = () => {
const [quizzes, setQuizzes] = useState<QuizTypeShort[]>([]);
const [monthlyQuizzes, setMonthlyQuizzes] = useState(0);
const [totalUsers, setTotalUsers] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUsers = async () => {
const fetchStats = async () => {
try {
const data = await ApiService.getQuizzes();
setQuizzes(data);
const data = await ApiService.getStats();
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) {
console.error("Error fetching quizzes:", error);
} finally {
setLoading(false);
}
};
fetchUsers();
fetchStats();
}, []);
const handleDelete = (id: string) => {
setQuizzes(quizzes.filter(quiz => quiz._id !== id));
};
const totalQuizzes = quizzes.length;
if (loading) {
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>
<TableHead>
<TableRow>
@ -51,6 +108,7 @@ const Users: React.FC = () => {
</TableBody>
</Table>
</TableContainer>
</Paper>
);
};

View file

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

View file

@ -19,11 +19,11 @@ class AdminController {
}
};
getQuizzes = async (req, res, next) => {
getStats = async (req, res, next) => {
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) {
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) => {
try {
const { id } = req.params;
if (!id) {
const { imgId } = req.query;
if (!imgId) {
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);
}
return res.status(200).json({ user: user });
return res.status(200).json({ deleted });
} catch (error) {
return next(error);
}

View file

@ -33,17 +33,25 @@ class Admin {
return deleted;
}
async getQuizzes() {
async getStats() {
await this.db.connect()
const conn = this.db.getConnection();
const usrColl = conn.collection('users');
const total = await usrColl.countDocuments();
const quizColl = conn.collection('files');
const projection = { content: 0, folderName: 0, folderId: 0 };
const result = await quizColl.find({}, projection).toArray();
if (!result) return null;
return result;
let respObj = {
quizzes: result,
total: total
}
return respObj;
}
async getImages(page, limit) {
@ -78,14 +86,14 @@ class Admin {
return respObj;
}
async deleteImage(uid, imgId) {
async deleteImage(imgId) {
let resp = false;
await this.db.connect()
const conn = this.db.getConnection();
const quizColl = conn.collection('files');
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){
const imgsColl = conn.collection('images');
const isDeleted = await imgsColl.deleteOne({ _id: ObjectId.createFromHexString(imgId) });

View file

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

View file

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