diff --git a/api/controllers/admin.js b/api/controllers/admin.js index b369b16..cb8cc9d 100644 --- a/api/controllers/admin.js +++ b/api/controllers/admin.js @@ -4,6 +4,50 @@ const { validationResult } = require('express-validator'); const errorHandling = require('../assets/utils/errorHandling'); const { serverError } = require('../assets/config/errors'); const Functions = require('../models/functions'); +const Categories = require('../models/categories'); +const helperQueryNumber = require('../assets/utils/helperQueryNumber'); +const Sequelize = require('sequelize'); + +exports.getFunctions = (req, res, next) => { + const page = helperQueryNumber(req.query.page, 1); + const limit = helperQueryNumber(req.query.limit, 10); + const categoryId = helperQueryNumber(req.query.categoryId, 0); + let search = req.query.search; + try { search = search.toLowerCase(); } catch {} + const offset = (page - 1) * limit; + Functions.findAndCountAll({ + limit, + offset, + where: { + // Trie par catégorie + ... (categoryId !== 0) && { categorieId: categoryId }, + // Recherche + ... (search != undefined) && { + [Sequelize.Op.or]: [ + { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, + { slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, + { description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } + ] + } + }, + include: [ + { model: Categories, attributes: ["name", "color"] } + ], + attributes: { + exclude: ["updatedAt", "utilizationForm", "article", "isOnline"] + }, + order: [['createdAt', 'DESC']] + }) + .then((result) => { + const { count, rows } = result; + const hasMore = (page * limit) < count; + return res.status(200).json({ totalItems: count, hasMore, rows }); + }) + .catch((error) => { + console.log(error); + return errorHandling(next, serverError); + }); +} exports.postFunction = (req, res, next) => { const { title, slug, description, type, categorieId } = req.body; @@ -19,7 +63,9 @@ exports.postFunction = (req, res, next) => { )) { return errorHandling(next, { message:"La fonction doit avoir une image valide.", statusCode: 400 }); } - const imageName = slug + image.name; + const splitedImageName = image.name.split('.'); + if (splitedImageName.length !== 2) return errorHandling(next, serverError); + const imageName = slug + '.' + splitedImageName[1]; image.mv(path.join(__dirname, '..', 'assets', 'images', 'functions') + '/' + imageName, async (error) => { if (error) return errorHandling(next, serverError); try { diff --git a/api/middlewares/isAdmin.js b/api/middlewares/isAdmin.js index 5802249..f0ac9e0 100644 --- a/api/middlewares/isAdmin.js +++ b/api/middlewares/isAdmin.js @@ -18,6 +18,6 @@ module.exports = (req, _res, next) => { }) .catch((error) => { console.log(error); - errorHandling(next, serverError); + return errorHandling(next, serverError); }); } \ No newline at end of file diff --git a/api/routes/admin.js b/api/routes/admin.js index 823fcf5..7134029 100644 --- a/api/routes/admin.js +++ b/api/routes/admin.js @@ -9,70 +9,95 @@ const Categories = require('../models/categories'); const AdminRouter = Router(); -// Permet de créé une fonction -AdminRouter.post('/functions', isAuth, isAdmin, -fileUpload({ - useTempFiles: true, - safeFileNames: true, - preserveExtension: Number, - limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, - parseNested: true -}), -[ - body('title') - .not() - .isEmpty() - .withMessage("La fonction doit avoir un titre.") - .isLength({ max: 100 }) - .withMessage("Le titre est trop long."), - body('slug') - .not() - .isEmpty() - .withMessage("La fonction doit avoir un slug.") - .isLength({ max: 100 }) - .withMessage("Le slug est trop long.") - .custom((async (slug) => { - try { - const FunctionSlug = await Functions.findOne({ where: { slug } }); - if (FunctionSlug) { - return Promise.reject("Le slug existe déjà..."); - } - } catch (error) { - console.log(error); - } - return true; - })), - body('description') - .not() - .isEmpty() - .withMessage("La fonction doit avoir une description.") - .isLength({ max: 255 }) - .withMessage("La description est trop longue."), - body('categorieId') - .not() - .isEmpty() - .withMessage("La fonction doit avoir une catégorie.") - .custom(async (categorieId) => { - try { - const categorieFound = await Categories.findOne({ where: { id: categorieId } }); - if (!categorieFound) { - return Promise.reject("La catégorie n'existe pas!"); - } - } catch (error) { - console.log(error); - } - return true; - }), - body('type') - .custom((type) => { - if (!(type === 'article' || type === 'form' || type === 'page')) { - return Promise.reject('Le type de la fonction peut être : article, form ou page.'); - } - return true; - }) -], adminController.postFunction); +AdminRouter.route('/functions') -// Supprime une fonction avec son id -AdminRouter.delete('/functions/:id', isAuth, isAdmin, adminController.deleteFunction); + // Récupère les fonctions + .get(isAuth, isAdmin, adminController.getFunctions) + + // Permet de créé une fonction + .post(isAuth, isAdmin, + fileUpload({ + useTempFiles: true, + safeFileNames: true, + preserveExtension: Number, + limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, + parseNested: true + }), + [ + body('title') + .not() + .isEmpty() + .withMessage("La fonction doit avoir un titre.") + .isLength({ max: 100 }) + .withMessage("Le titre est trop long.") + .custom(((title) => { + if (title === 'undefined') { + return Promise.reject("La fonction doit avoir un titre."); + } + return true; + })), + body('slug') + .not() + .isEmpty() + .withMessage("La fonction doit avoir un slug.") + .isLength({ max: 100 }) + .withMessage("Le slug est trop long.") + .custom(((slug) => { + if (slug === 'undefined') { + return Promise.reject("La fonction doit avoir un slug."); + } + return true; + })) + .custom((async (slug) => { + try { + const FunctionSlug = await Functions.findOne({ where: { slug } }); + if (FunctionSlug) { + return Promise.reject("Le slug existe déjà..."); + } + } catch (error) { + console.log(error); + } + return true; + })), + body('description') + .not() + .isEmpty() + .withMessage("La fonction doit avoir une description.") + .isLength({ max: 255, min: 1 }) + .withMessage("La description est trop longue.") + .custom(((description) => { + if (description === 'undefined') { + return Promise.reject("La fonction doit avoir une description."); + } + return true; + })), + body('categorieId') + .not() + .isEmpty() + .withMessage("La fonction doit avoir une catégorie.") + .custom(async (categorieId) => { + try { + const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }); + if (!categorieFound) { + return Promise.reject("La catégorie n'existe pas!"); + } + } catch (error) { + console.log(error); + } + return true; + }), + body('type') + .custom((type) => { + if (!(type === 'article' || type === 'form' || type === 'page')) { + return Promise.reject('Le type de la fonction peut être : article, form ou page.'); + } + return true; + }) + ], adminController.postFunction); + +AdminRouter.route('/functions/:id') + + // Supprime une fonction avec son id + .delete(isAuth, isAdmin, adminController.deleteFunction); module.exports = AdminRouter; \ No newline at end of file diff --git a/website/components/FunctionsList/FunctionsList.js b/website/components/FunctionsList/FunctionsList.js index faaa532..abdef26 100644 --- a/website/components/FunctionsList/FunctionsList.js +++ b/website/components/FunctionsList/FunctionsList.js @@ -55,7 +55,12 @@ const FunctionsList = (props) => { const getFunctionsData = () => { setLoadingFunctions(true); return new Promise(async (next) => { - const result = await api.get(`/functions?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`); + const URL = `${(props.isAdmin) ? "/admin/functions" : "/functions"}?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}`; + const result = await api.get(URL, { + headers: { + ...(props.isAdmin && props.token != undefined) && { 'Authorization': props.token } + } + }); setLoadingFunctions(false); next(result.data); }); diff --git a/website/pages/admin/addFunction.js b/website/pages/admin/addFunction.js deleted file mode 100644 index e70e1a5..0000000 --- a/website/pages/admin/addFunction.js +++ /dev/null @@ -1,18 +0,0 @@ -import Cookies from "universal-cookie"; - -const addFunction = (props) => { - return ( -
Crée une nouvelle fonction
- ); -} - -export async function getServerSideProps({ req }) { - const cookies = new Cookies(req.headers.cookie); - return { - props: { - user: { ...cookies.get('user') } - } - }; -} - -export default addFunction; \ No newline at end of file diff --git a/website/pages/admin/index.js b/website/pages/admin/index.js index 89ec17a..cc22b3b 100644 --- a/website/pages/admin/index.js +++ b/website/pages/admin/index.js @@ -1,12 +1,66 @@ -import { Fragment } from 'react'; -import Link from 'next/link'; +import { Fragment, useState, useEffect } from 'react'; import Cookies from "universal-cookie"; import HeadTag from '../../components/HeadTag'; import FunctionsList from '../../components/FunctionsList/FunctionsList'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import Modal from '../../components/Modal'; import redirect from '../../utils/redirect'; +import htmlParser from 'html-react-parser'; +import Loader from '../../components/Loader'; +import useAPI from '../../hooks/useAPI'; +import '../../public/css/pages/admin.css'; +import api from '../../utils/api'; const Admin = (props) => { + const [, categories] = useAPI('/categories'); + const [isOpen, setIsOpen] = useState(false); + const [inputState, setInputState] = useState({ type: 'form' }); + const [message, setMessage] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (categories.length > 0) { + handleChange({ + target: { + name: "categorieId", + value: categories[0].id + } + }); + } + }, [categories]); + + const toggleModal = () => setIsOpen(!isOpen); + + const handleChange = (event, isTypeCheck = false) => { + const inputStateNew = { ...inputState }; + inputStateNew[event.target.name] = (event.target.files != undefined) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value; + setInputState(inputStateNew); + } + + const handleSubmit = (event) => { + event.preventDefault(); + setIsLoading(true); + const formData = new FormData(); + formData.append('type', inputState.type); + formData.append('categorieId', inputState.categorieId); + formData.append('title', inputState.title); + formData.append('slug', inputState.slug); + formData.append('description', inputState.description); + formData.append('image', inputState.image); + + api.post('/admin/functions', formData, { headers: { 'Authorization': props.user.token } }) + .then((response) => { + setMessage(`Succès: ${response.data.message}
`); + setIsLoading(false); + }) + .catch((error) => { + setMessage(`Erreur: ${error.response.data.message}
`); + setIsLoading(false); + }); + } + if (!props.user.isAdmin && typeof window != 'undefined') { return redirect({}, '/404'); } @@ -15,14 +69,89 @@ const Admin = (props) => {(Vous devrez vous reconnecter après la sauvegarde)
Si vous changez votre adresse email, vous devrez la confirmer comme à l'inscription (vérifier vos emails).
(Vous devrez vous reconnecter après la sauvegarde)
Si vous changez votre adresse email, vous devrez la confirmer comme à l'inscription (vérifier vos emails).