diff --git a/app.js b/app.js index 9beabb8..d5cd674 100644 --- a/app.js +++ b/app.js @@ -5,6 +5,9 @@ const os = require('os') const mongoose = require('mongoose') require('dotenv').config() +/** + * подключение переменных среды + */ const devMode = process.env.NODE_ENV === "dev" const PORT = process.env.PORT || 5000 const mongoUri = process.env.mongoUri @@ -14,11 +17,17 @@ const app = express() app.use(express.json({ extended: true })) +/** + * подключение роутов + */ app.use('/api/auth', require('./routes/auth.routes')) app.use('/api/notes', require('./routes/notes.routes')) if (httpsRedirect) app.use(httpToHttps) +/** + * подключение статической библиотеки клиента + */ if (!devMode) { app.use('/', express.static(path.join(__dirname, 'client', 'build'))) app.get('*', (req, res) => { @@ -30,6 +39,9 @@ if (!devMode) { }) } +/** + * запуск сервера + */ async function start() { try { connectMongo(mongoUri) @@ -42,6 +54,10 @@ async function start() { start() +/** + * подключение к MongoDb + * @param {*} mongoUri + */ async function connectMongo(mongoUri) { if (mongoUri) { await mongoose.connect(mongoUri, { @@ -54,6 +70,9 @@ async function connectMongo(mongoUri) { } } +/** + * Вывод информации о сервере + */ function logServerStart() { dns.lookup(os.hostname(), (err, address) => { const [logName, sBef, sAft] = devMode ? ['Express server', ' ', ':'] : ['React Notes App', '-', ''] @@ -64,6 +83,9 @@ function logServerStart() { }) } +/** + * перенаправление с http на https для PWA + */ function httpToHttps(req, res, next) { if (req.header('x-forwarded-proto') !== 'https') { res.redirect(`https://${req.header('host')}${req.url}`) diff --git a/client/src/App.js b/client/src/App.js index e3779cc..c05b71e 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -12,10 +12,14 @@ import { PageContext } from './Context/PageContext' import Header from './Pages/SharedComponents/Header' function App() { + /**подключение хука авторизации */ const { token, login, logout, userId, email, ready } = useAuth() const isAuthenticated = !!token + + /**подключение хука роутов */ const routes = useRoutes(isAuthenticated) + /**хук обновления навбара */ const [nav, setNav] = React.useState() if (!ready) { @@ -26,20 +30,23 @@ function App() { ) } - + /**рендер */ return ( + /** + * обертка в контексты авторизации и обноления хедера + * внутри роутер со статичным хедером и динамическим содержимым + * */ - + -
{nav}
-
- {routes} -
-
+
{nav}
+
+ {routes} +
+
-
) } diff --git a/client/src/Cards/AddCard.js b/client/src/Cards/AddCard.js index db945a3..ac48005 100644 --- a/client/src/Cards/AddCard.js +++ b/client/src/Cards/AddCard.js @@ -4,12 +4,16 @@ import Palette, { colors } from './palette/palette' import useInputValue from '../Hooks/useInputValue.hook' import CardsContext from '../Context/CardsContext' +/**Компонент добавления новой заметки */ function AddCard() { + /**контекст*/ const { addCard } = React.useContext(CardsContext) + /**хук инпута*/ const input = useInputValue('') const defColor = colors[0] const [color, setColor] = React.useState(defColor) + /**проверка темного фона */ const blackOnHover = () => { switch (color) { case colors[0]: @@ -24,6 +28,7 @@ function AddCard() { } } + /**обработчик добавления заметки */ function submitHandler() { if (String(input.value).trim() && String(color).trim()) { addCard({ name: String(input.value).trim(), text: "", color: String(color) }) @@ -37,6 +42,7 @@ function AddCard() { submitHandler() } + /**рендер */ return (
diff --git a/client/src/Cards/CardItem.js b/client/src/Cards/CardItem.js index c2bb4e1..c959fbc 100644 --- a/client/src/Cards/CardItem.js +++ b/client/src/Cards/CardItem.js @@ -5,17 +5,24 @@ import Card, { PropTypeCard } from './cardType/Card' import ReactMarkdown from 'react-markdown' import gfm from 'remark-gfm' +/**исправление переноса строки в markdown */ function fixLineBreaks(mdStr) { return String(mdStr).replace(/\n/gi, ' \n') } +/** + * Компонент заметки + * @param {*} param0 + * @returns + */ function CardItem({ card = new Card(), index }) { + /**Подключение контекста */ const { removeCard, setEditCard } = useContext(CardsContext) + const lineClip = 12 const bgColor = card.color return ( -
@@ -44,10 +51,10 @@ function CardItem({ card = new Card(), index }) {
- ) } +// Валидация CardItem.propTypes = { card: PropTypeCard.isRequired, index: PropTypes.number diff --git a/client/src/Cards/CardList.js b/client/src/Cards/CardList.js index 7cf2dbf..8b41f45 100644 --- a/client/src/Cards/CardList.js +++ b/client/src/Cards/CardList.js @@ -5,6 +5,7 @@ import StackGrid, { transitions } from "react-stack-grid" import sizeMe from 'react-sizeme' const { scaleDown } = transitions +/**расчет ширины столбцов */ function calcWidth() { const small = 576 const middle = 768 @@ -19,14 +20,17 @@ function calcWidth() { else return '100%' } +/** Компонент списка карточек */ function CardList(props) { const grid = React.useRef(null) React.useEffect(gridRedraw, [props.size]) + /**обновление рендера */ function gridRedraw() { setTimeout(() => { if (grid.current && grid.current.updateLayout) grid.current.updateLayout() }, 10) } + /**параметры сетки */ const gridSettings = { ref: grid, columnWidth: calcWidth(), @@ -40,6 +44,7 @@ function CardList(props) { leaved: scaleDown.leaved } + /**рендер */ return (
@@ -53,6 +58,7 @@ function CardList(props) { ) } +// Валидация CardList.propTypes = { cards: PropTypes.arrayOf(PropTypes.object).isRequired, } diff --git a/client/src/Cards/ModalCardEdit.js b/client/src/Cards/ModalCardEdit.js index e06d22e..687f4e6 100644 --- a/client/src/Cards/ModalCardEdit.js +++ b/client/src/Cards/ModalCardEdit.js @@ -6,45 +6,58 @@ import Modal, { ModalProps } from "../Shared/Modal/Modal" import Card, { PropTypeCard } from './cardType/Card' import Palette from './palette/palette' +/**расчет числа строк */ function calcMaxRows() { const small = 576 const middle = 768 const large = 992 const winWidth = window.innerWidth - if (winWidth < small) return '7' else if (winWidth < middle) return '8' else if (winWidth < large) return '10' else return '17' } +/** + * Модальное окно редактирования заметки + * @param {*} param0 + * @returns + */ function ModalCardEdit({ card = new Card(), index }) { + /**получение контекста */ const { removeCard, changeCardColor, unsetEditCard, editCardContent } = React.useContext(CardsContext) React.useEffect(() => { if (card !== null) open() }, [card]) + /**хук состояния формы */ const [showForm, setShowForm] = React.useState(false) + /**создание параметров модального окна*/ const modalProps = new ModalProps() modalProps.isOpen = showForm modalProps.setOpenState = setShowForm modalProps.sideClose = true modalProps.onSideClick = unsetEditCard + /**открытие окна */ function open() { setShowForm(true) } + /**закрытие окна */ function close() { setShowForm(false) } - ///// - + /**сохранение данных */ function save(name, text) { editCardContent(index, name, text) } + /** + * обраюотчик изменений инпута + * @param {*} e + */ function onInputChange(e) { let name = card.name let text = card.text @@ -53,19 +66,32 @@ function ModalCardEdit({ card = new Card(), index }) { save(name, text) } + /** + * Изменение цвета + * @param {*} color + */ function tryChangeColor(color) { changeCardColor(index, color) } + + /** + * удаление + */ function tryRemove() { unsetEditCard(); close(); removeCard(index); } + + /** + * закрытие и сброс окна + */ function tryClose() { unsetEditCard() close() } + /**рендер */ return (
@@ -135,6 +161,7 @@ function ModalCardEdit({ card = new Card(), index }) { ) } +// Валидация ModalCardEdit.propTypes = { card: PropTypeCard, index: PropTypes.number, diff --git a/client/src/Cards/cardType/Card.js b/client/src/Cards/cardType/Card.js index 33e87c0..891aa9f 100644 --- a/client/src/Cards/cardType/Card.js +++ b/client/src/Cards/cardType/Card.js @@ -1,12 +1,14 @@ import PropTypes from 'prop-types' +/**валидация пропсов заметки*/ export const PropTypeCard = PropTypes.shape({ - id: PropTypes.string, + id: PropTypes.string, name: PropTypes.string, color: PropTypes.string, text: PropTypes.string, }) +/**валидация заметки */ export function checkCard(card) { return ( (typeof card.id === "string") && @@ -16,6 +18,7 @@ export function checkCard(card) { ) } +/**валидация массива заметок */ export function checkCardsArr(cardsArr) { if (!Array.isArray(cardsArr)) return false else if (cardsArr.length === 0) return true @@ -29,6 +32,7 @@ export function checkCardsArr(cardsArr) { } } +/**класс заметки */ export class Card { constructor({ id, name, color, text }) { this.id = String(id) diff --git a/client/src/Cards/palette/palette.js b/client/src/Cards/palette/palette.js index 5373f70..7ce7d60 100644 --- a/client/src/Cards/palette/palette.js +++ b/client/src/Cards/palette/palette.js @@ -2,6 +2,7 @@ import React from "react"; import PropTypes from 'prop-types' import "./palette.css" +/**набор цветов */ export const colors = [ "#f8f9fa", "#F38181", @@ -14,7 +15,11 @@ export const colors = [ "#6EF3D6" ]; - +/** + * компонент палитры + * @param {*} param0 + * @returns + */ function Palette({ setColor, style, className, disabled }) { return ( @@ -59,6 +64,7 @@ function Palette({ setColor, style, className, disabled }) { ); } +// Валидация Palette.propTypes = { setColor: PropTypes.func, style: PropTypes.object, diff --git a/client/src/Context/AuthContext.js b/client/src/Context/AuthContext.js index b302288..2260055 100644 --- a/client/src/Context/AuthContext.js +++ b/client/src/Context/AuthContext.js @@ -2,6 +2,7 @@ import {createContext} from 'react' function noop() {} +/**контекст авторизации */ export const AuthContext = createContext({ token: null, userId: null, diff --git a/client/src/Context/CardsContext.js b/client/src/Context/CardsContext.js index 084eb5e..488ee50 100644 --- a/client/src/Context/CardsContext.js +++ b/client/src/Context/CardsContext.js @@ -1,5 +1,6 @@ import {createContext} from 'react' +/**контекст заметок */ const CardsContext = createContext() export default CardsContext \ No newline at end of file diff --git a/client/src/Context/PageContext.js b/client/src/Context/PageContext.js index 0410d50..25112b4 100644 --- a/client/src/Context/PageContext.js +++ b/client/src/Context/PageContext.js @@ -2,4 +2,5 @@ import { createContext } from 'react' function noop() { } +/**контекст данных страницы */ export const PageContext = createContext({ setNav: noop }) diff --git a/client/src/Hooks/auth.hook.js b/client/src/Hooks/auth.hook.js index 588548f..fddc1ff 100644 --- a/client/src/Hooks/auth.hook.js +++ b/client/src/Hooks/auth.hook.js @@ -1,36 +1,41 @@ -import {useState, useCallback, useEffect} from 'react' +import { useState, useCallback, useEffect } from 'react' const storageName = 'userData' +/** + * Хук авторизации + * @returns + */ export const useAuth = () => { const [token, setToken] = useState(null) const [ready, setReady] = useState(false) const [userId, setUserId] = useState(null) const [email, setEmail] = useState(null) + /**обновление данных авторизации */ const login = useCallback((jwtToken, id, email) => { setToken(jwtToken) setUserId(id) setEmail(email) - + /**запись данных в кэш */ localStorage.setItem(storageName, JSON.stringify({ userId: id, token: jwtToken, email: email })) - }, []) - + /**очистка данных авторизации */ const logout = useCallback(() => { - setToken(null) setUserId(null) setEmail(null) + /**удаление данных из кэша */ localStorage.removeItem(storageName) }, []) + /**считывание данных из кэша */ useEffect(() => { + /**данные из кэша */ const data = JSON.parse(localStorage.getItem(storageName)) - if (data && data.token) { login(data.token, data.userId, data.email) } diff --git a/client/src/Hooks/http.hook.js b/client/src/Hooks/http.hook.js index 4c8fec0..950f0ca 100644 --- a/client/src/Hooks/http.hook.js +++ b/client/src/Hooks/http.hook.js @@ -1,5 +1,10 @@ -import {useState, useCallback} from 'react' +import { useState, useCallback } from 'react' +/** + * Хук обработки Http запросов + * Позволяет выводить результаты, ошибки и статус запроса, удобно совершать параметрические запросы + * @returns + */ export const useHttp = () => { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) @@ -7,20 +12,19 @@ export const useHttp = () => { const request = useCallback(async (url, method = 'GET', body = null, headers = {}) => { setLoading(true) try { + /**составление запроса */ if (body) { body = JSON.stringify(body) headers['Content-Type'] = 'application/json' } - - const response = await fetch(url, {method, body, headers}) + /**отправка запроса */ + const response = await fetch(url, { method, body, headers }) + /**получение ответа */ const data = await response.json() - if (!response.ok) { throw new Error(data.message || 'Что-то пошло не так') } - setLoading(false) - return data } catch (e) { setLoading(false) diff --git a/client/src/Hooks/useDebouncedEffect.hook.js b/client/src/Hooks/useDebouncedEffect.hook.js index 4217d32..04ea5a6 100644 --- a/client/src/Hooks/useDebouncedEffect.hook.js +++ b/client/src/Hooks/useDebouncedEffect.hook.js @@ -1,28 +1,33 @@ import { useState, useEffect } from 'react'; +/** + * Хук debounce + * @param {*} value + * @param {*} delay + * @returns + */ function useDebounce(value, delay) { - const [debouncedValue, setDebouncedValue] = useState(value); - useEffect( () => { - const handler = setTimeout(() => { setDebouncedValue(value); }, delay); - - return () => { clearTimeout(handler); }; }, - [delay, value] ); - return debouncedValue; } +/** + * Хук debounced effect + * @param {*} func + * @param {*} deps + * @param {*} delay + */ export default function useDebouncedEffect(func, deps, delay) { const debounced = useDebounce(deps, delay || 0) const debDepsList = Array.isArray(debounced) ? debounced : debounced ? [debounced] : undefined diff --git a/client/src/Hooks/useInputValue.hook.js b/client/src/Hooks/useInputValue.hook.js index 45093e5..eb592a0 100644 --- a/client/src/Hooks/useInputValue.hook.js +++ b/client/src/Hooks/useInputValue.hook.js @@ -1,5 +1,10 @@ import { useState } from 'react' +/** + * Хук для обработки форм + * @param {*} defaultValue + * @returns + */ export default function useInputValue(defaultValue) { const [value, setValue] = useState(defaultValue) return { diff --git a/client/src/Pages/AuthPage.js b/client/src/Pages/AuthPage.js index f1a1180..8990e81 100644 --- a/client/src/Pages/AuthPage.js +++ b/client/src/Pages/AuthPage.js @@ -1,32 +1,49 @@ import React, { useContext, useEffect, useState } from 'react' import { useHttp } from '../Hooks/http.hook' - import { AuthContext } from '../Context/AuthContext' import { PageContext } from '../Context/PageContext' - import { NavLink, useHistory } from 'react-router-dom' import './AuthPage.css' +/** + * Страница авторизации + * @returns + */ function AuthPage() { + /**подключение контекстов */ const auth = useContext(AuthContext) const page = useContext(PageContext) + const history = useHistory() + /**подключение хука http запросов */ const { loading, request, error, clearError } = useHttp() + + /**состояние формы */ const [form, setForm] = useState({ email: '', password: '' }) + + /**хук сообщений от сервера */ const [message, setMessage] = useState(null) + /** очистка оштбок хука запросов и запись ошибки в сообщение*/ useEffect(() => { if (error) setMessage([error, false]) clearError() }, [error, clearError]) + /** + * обработчик ввода данных в форму + * @param {*} event + */ const changeHandler = event => { setForm({ ...form, [event.target.name]: event.target.value }) } + /** + * обработчик регистрации + */ const registerHandler = async () => { try { const data = await request('/api/auth/register', 'POST', { ...form }) @@ -34,6 +51,9 @@ function AuthPage() { } catch (e) { } } + /** + * Обработчик входа в систему + */ const loginHandler = async () => { try { const data = await request('/api/auth/login', 'POST', { ...form }) @@ -42,11 +62,17 @@ function AuthPage() { } catch (e) { } } + /** + * Обработчик выхода + */ const logoutHandler = event => { event.preventDefault() auth.logout() } + /** + * Обновление навбара при переходе на эту страницу и изменениях + */ React.useEffect(() => { page.setNav(auth.isAuthenticated && @@ -56,10 +82,11 @@ function AuthPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [auth.isAuthenticated, auth.token]) + /**рендер */ return ( + /**Здесь отрисовываются меню регистрации и авторизации */
-
@@ -76,14 +103,12 @@ function AuthPage() {
- { - message && + {message &&
{message[1] ? "Инфо" : "Ошибка"} {String(message[0])}
} - e.key === 'Enter' && loginHandler()} /> - {!auth.isAuthenticated ? ( -
-
-
) : ( -
-
)}
-
) diff --git a/client/src/Pages/CardsPage.js b/client/src/Pages/CardsPage.js index 53aa4a8..abefc9c 100644 --- a/client/src/Pages/CardsPage.js +++ b/client/src/Pages/CardsPage.js @@ -1,54 +1,67 @@ import React from 'react'; - import './CardsPage.css'; import CardList from '../Cards/CardList' import AddCard from '../Cards/AddCard' import CardsContext from '../Context/CardsContext' import Loader from '../Shared/Loader' import ModalCardEdit from '../Cards/ModalCardEdit' - import Card, { checkCardsArr } from '../Cards/cardType/Card' - - import { NavLink } from 'react-router-dom' import { AuthContext } from '../Context/AuthContext' import { PageContext } from '../Context/PageContext' - import { useHttp } from '../Hooks/http.hook' +/** + * Хук использования массива заметок + * @param {*} defaultValue + * @returns + */ function useCardsArr(defaultValue) { const [value, setValue] = React.useState(defaultValue) - function trySetValue(cardsArr) { if (checkCardsArr(cardsArr) || cardsArr === null) setValue(cardsArr) else console.error('Массив cardsArr не прошел проверку \n', cardsArr) } - return [value, trySetValue] } +/** + * Хук-таймер для обновления данных с очисткой счетчика при ререндере + * @returns + */ function useUpdater() { const [updaterVal, setUpdaterVal] = React.useState(null) const timer = React.useRef() React.useEffect(() => { - if (timer.current) clearTimeout(timer.current) + if (timer.current) clearTimeout(timer.current) // сброс при переопределении таймера timer.current = setTimeout(() => { console.log("Timed update") setUpdaterVal(Date.now()) }, 60 * 1000) // обновяем через минуту - return () => clearTimeout(timer.current) + return () => clearTimeout(timer.current) // сброс при ререндере }, []) // eslint-disable-line react-hooks/exhaustive-deps return [updaterVal] } +/** + * Страница с заметками + * @returns + */ function CardsPage() { - + /**подключение контекстов */ const auth = React.useContext(AuthContext) const page = React.useContext(PageContext) + /**подключение хука http запросов */ const { loading, request, error, clearError } = useHttp() + + /**хук сообщений от сервера */ + const [message, setMessage] = React.useState(null) + + /**Хук-функция для работы с базой данных заметок */ const fetchNotes = React.useCallback(async (url = "", method = "GET", body = null, resCallback = () => { }) => { try { + /**запрос к серверу с определенными параметрами*/ const fetched = await request(`/api/notes${url ? ("/" + url) : ""}`, method, body, { Authorization: `Bearer ${auth.token}` }) resCallback(tryParce(fetched)) } catch (e) { } @@ -61,42 +74,52 @@ function CardsPage() { } }, [auth.token, request]) - const [message, setMessage] = React.useState(null) - + /** очистка оштбок хука запросов и запись ошибки в сообщение*/ React.useEffect(() => { if (error) setMessage([error, false]) clearError() }, [error, clearError]) - - + /**Массив заметок */ const [cardsArr, setCardsArr] = useCardsArr(null) + + /**Id редактируемой заметки */ const [editCardId, setEditCardId] = React.useState(null) const [updaterVal] = useUpdater() - const updatingEnable = React.useRef(true) + /** + * хук обновления данных с сервера + * флаг updatingEnable позволяет избежать взаимодействия с устаревшей unmount версией компонента + */ React.useEffect(() => { updatingEnable.current = true loadDataFromServer() return () => updatingEnable.current = false }, [auth.isAuthenticated, auth.email, updaterVal]) // eslint-disable-line react-hooks/exhaustive-deps - React.useEffect(clearOldData, [auth.isAuthenticated]) // eslint-disable-line react-hooks/exhaustive-deps - /////////// + //очистка старых данных + React.useEffect(clearOldData, [auth.isAuthenticated]) // eslint-disable-line react-hooks/exhaustive-deps function clearOldData() { - //console.log("clearOldData, auth.isAuthenticated:", auth.isAuthenticated) if (!auth.isAuthenticated) setCardsArr(null) } /////////// /////////// + + /** + * получение данных с сервера + */ function loadDataFromServer() { fetchNotes("", "GET", null, setLoadedCards) } + /** + * Внесение в полученных данных в массив + * @param {*} cards + */ function setLoadedCards(cards) { if (updatingEnable.current) setCardsArr([...cards]) } @@ -104,16 +127,29 @@ function CardsPage() { /////////// + /** + * Загрузка данных на сервер + * @param {*} card + * @param {*} target + */ function loadDataToServer(card = new Card(), target = 'set') { fetchNotes(target, "POST", { card }) } + /** + * удаление карточки + * @param {*} index + */ function removeCard(index) { const toDelete = cardsArr.splice(index, 1)[0] setCardsArr([...cardsArr]) loadDataToServer(toDelete, "delete") } + /** + * добавление карточки + * @param {*} cardData + */ function addCard(cardData = {}) { const newId = String(auth.email) + String(Date.now()) + String(Math.random()) const newCard = new Card({ id: newId, name: cardData.name, color: cardData.color, text: cardData.text }) @@ -125,12 +161,23 @@ function CardsPage() { } + /** + * Изменение цвета карточки + * @param {*} index + * @param {*} color + */ function changeCardColor(index, color) { cardsArr[index].color = color setCardsArr([...cardsArr]) loadDataToServer(cardsArr[index], "set") } + /** + * Изменение текстового содержания карточки + * @param {*} index + * @param {*} name + * @param {*} text + */ function editCardContent(index, name, text) { if (cardsArr[index]) { let card = new Card(cardsArr[index]) @@ -144,17 +191,23 @@ function CardsPage() { /////////// /////////// - function getCardByIndex(index) { - return index !== null ? cardsArr[index] : null - } + /**функция назначения редактируемой заметки для модального окна */ function setEditCard(index) { setEditCardId(index) } + /**функция сброса редактируемой заметки для модального окна */ function unsetEditCard() { setEditCardId(null) } + /**функция получения карточки по id */ + function getCardByIndex(index) { + return index !== null ? cardsArr[index] : null + } /////////// + /** + * Обновление навбара при переходе на эту страницу и изменениях + */ React.useEffect(() => { page.setNav( @@ -162,7 +215,6 @@ function CardsPage() { {loading ? : } Update - {auth.email} @@ -171,11 +223,13 @@ function CardsPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [auth.email, auth.token]) + /**рендер */ return ( + /**Здесь отрисовываются меню добавления и редактирования заметок и сам перечнь заметок в виде динамичной отзывчивой сетки */
-
+ @@ -197,6 +251,7 @@ function CardsPage() {
} +
diff --git a/client/src/Pages/SharedComponents/Header.js b/client/src/Pages/SharedComponents/Header.js index a877ed5..a1473ee 100644 --- a/client/src/Pages/SharedComponents/Header.js +++ b/client/src/Pages/SharedComponents/Header.js @@ -4,10 +4,9 @@ import './Header.css'; const brandText = "Notes" +/**компонент хедера с логотипом и навбаром */ function Header(props) { - return ( -
- ); } diff --git a/client/src/Shared/Loader.js b/client/src/Shared/Loader.js index d3badc4..df32e50 100644 --- a/client/src/Shared/Loader.js +++ b/client/src/Shared/Loader.js @@ -1,5 +1,6 @@ import React from 'react' +/**просто вращающееся колесико загрузки */ export default function Loader({ className }) { return ( diff --git a/client/src/Shared/Modal/Modal.js b/client/src/Shared/Modal/Modal.js index 6113632..6d7bed4 100644 --- a/client/src/Shared/Modal/Modal.js +++ b/client/src/Shared/Modal/Modal.js @@ -7,7 +7,13 @@ const classes = { modalBody: 'modal-window-body' } +/** + * Компонент-остнова для модального окна + * @param {{ isOpen, setOpenState, openButton, sideClose, onSideClick }} props + * @returns + */ function Modal(props) { + /**получение параметров */ const { isOpen, setOpenState, openButton, sideClose, onSideClick } = props const wrapperRef = React.useRef(null) @@ -21,16 +27,19 @@ function Modal(props) { if (typeof onSideClick === "function") onSideClick() } + /**обработчик клика по кнопке открытия */ function handleOpenButtonClick() { open() } + /**обработчик клика вне окна */ function handleWrapperClick(e) { if (wrapperRef.current && wrapperRef.current === e.target) { if (sideClose) close() } } + /**рендер */ return ( {openButton && @@ -57,6 +66,9 @@ function Modal(props) { ) } +/** + * Класс для удобного создания обьекта параметров модального окна + */ class ModalProps { constructor() { this.isOpen = false @@ -77,9 +89,9 @@ class ModalProps { } } +/**валидация параметров */ Modal.propTypes = { children: PropTypes.object, - isOpen: PropTypes.bool, setOpenState: PropTypes.func, openButton: PropTypes.bool, diff --git a/client/src/index.js b/client/src/index.js index 688e033..9434a09 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -9,6 +9,7 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import "bootstrap/dist/js/bootstrap.bundle.min"; +/**рендер приложения */ ReactDOM.render( @@ -19,10 +20,18 @@ ReactDOM.render( // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://cra.link/PWA +/** + * Подключение ServiceWorker для PWA + */ serviceWorkerRegistration.register({ + /**подключение обновителя */ onUpdate: updater }); +/** + * Обновление кэша PWA + * @param {*} registration + */ function updater(registration) { alert('New version available! Ready to update?'); if (registration && registration.waiting) { diff --git a/client/src/routes.js b/client/src/routes.js index 302cb6f..12eddf8 100644 --- a/client/src/routes.js +++ b/client/src/routes.js @@ -2,27 +2,26 @@ import React from 'react' import { Switch, Route, Redirect } from 'react-router-dom' import CardsPage from './Pages/CardsPage' - import AuthPage from './Pages/AuthPage' export const useRoutes = isAuthenticated => { if (isAuthenticated) { return ( + /**Набор роутов в случае авторизации */ - - ) } return ( + /**Набор роутов в случае неавторизации */ diff --git a/middleware/auth.middleware.js b/middleware/auth.middleware.js index 9358fbb..26713fa 100644 --- a/middleware/auth.middleware.js +++ b/middleware/auth.middleware.js @@ -1,19 +1,29 @@ const jwt = require('jsonwebtoken') require('dotenv').config() +/** + * Функция-Middleware для проверки авторизации пользователя + * @param {*} req + * @param {*} res + * @param {*} next + * @returns + */ module.exports = (req, res, next) => { + //проверка работы сервера if (req.method === 'OPTIONS') { return next() } try { - + /**получение токена */ const token = req.headers.authorization.split(' ')[1] // "Bearer TOKEN" + /**проверка отсутствия токена */ if (!token) { return res.status(401).json({ message: 'Нет авторизации' }) } + /**верификация токена */ const decoded = jwt.verify(token, process.env.jwtSecret) req.user = decoded next() diff --git a/models/Note.js b/models/Note.js index 4262ca8..fae8858 100644 --- a/models/Note.js +++ b/models/Note.js @@ -1,12 +1,14 @@ const { Schema, model, Types } = require('mongoose') +/** + * Схема заметки для базы данных + */ const schema = new Schema({ - id: { type: String, required: true, unique: true /*, default: String(Date.now) + String(Math.random)*/ }, + id: { type: String, required: true, unique: true }, name: { type: String }, text: { type: String }, color: { type: String }, image: { type: String }, - //date: { type: Date, default: Date.now }, owner: { type: Types.ObjectId, ref: 'User', required: true } }) diff --git a/models/User.js b/models/User.js index 3bc1240..a36a7a4 100644 --- a/models/User.js +++ b/models/User.js @@ -1,5 +1,8 @@ const {Schema, model, Types} = require('mongoose') +/** + * Схема данных пользователя + */ const schema = new Schema({ email: {type: String, required: true, unique: true}, password: {type: String, required: true}, diff --git a/routes/auth.routes.js b/routes/auth.routes.js index 380f8c0..736e07a 100644 --- a/routes/auth.routes.js +++ b/routes/auth.routes.js @@ -6,18 +6,22 @@ const { check, validationResult } = require('express-validator') const User = require('../models/User') const router = Router() -// /api/auth/register +/** + * Регистрация + * /api/auth/register + */ router.post( '/register', [ + /**валидация */ check('email', 'Некорректный email').isEmail(), check('password', 'Минимальная длина пароля 6 символов') .isLength({ min: 6 }) ], async (req, res) => { try { + /**проверка данных */ const errors = validationResult(req) - if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array(), @@ -27,15 +31,15 @@ router.post( const { email, password } = req.body + /**Проверка существования пользователя */ const candidate = await User.findOne({ email }) - if (candidate) { return res.status(400).json({ message: 'Такой пользователь уже существует' }) } + /**Хеширование пароля и сохранение пользователя */ const hashedPassword = await bcrypt.hash(password, 12) const user = new User({ email, password: hashedPassword }) - await user.save() res.status(201).json({ message: 'Пользователь создан' }) @@ -45,17 +49,21 @@ router.post( } }) -// /api/auth/login +/** + * Вход + * /api/auth/login + */ router.post( '/login', [ + /**валидация */ check('email', 'Введите корректный email').normalizeEmail().isEmail(), check('password', 'Введите пароль').exists() ], async (req, res) => { try { + /**проверка данных */ const errors = validationResult(req) - if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array(), @@ -65,18 +73,19 @@ router.post( const { email, password } = req.body + /**Поиск пользователя в бд */ const user = await User.findOne({ email }) - if (!user) { return res.status(400).json({ message: 'Пользователь не найден' }) } + /**Проверка пароля */ const isMatch = await bcrypt.compare(password, user.password) - if (!isMatch) { return res.status(400).json({ message: 'Неверный пароль, попробуйте снова' }) } + /**Создание токена */ const token = jwt.sign( { userId: user.id }, process.env.jwtSecret, diff --git a/routes/notes.routes.js b/routes/notes.routes.js index da913ac..18e1fa0 100644 --- a/routes/notes.routes.js +++ b/routes/notes.routes.js @@ -6,9 +6,13 @@ const router = Router() const { checkCard } = require('../validation/CardCheck') - +/** + * Добавление и редактирование заметки + * /api/notes/set + */ router.post('/set', auth, async (req, res) => { try { + /**получение данных о заметке и запись в бд */ const card = tryParce(req.body.card) if (checkCard(card)) { postNote(card) @@ -20,18 +24,20 @@ router.post('/set', auth, async (req, res) => { res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' }) } + /**Добавление или редактирование заметки в бд */ async function postNote(noteToSave) { noteToSave.owner = req.user.userId + /**проверка существования заметки */ const existing = await Note.findOne({ id: noteToSave.id }) if (existing) { - //console.log("EXITING"); + /**Выполнится если такая заметка уже есть */ existing.overwrite(noteToSave) existing.save() } else { - //console.log("NON EXITING") + /**Выполнится если нет такой заметки */ const note = new Note(noteToSave) await note.save() } @@ -40,10 +46,13 @@ router.post('/set', auth, async (req, res) => { }) - +/** + * Удаление заметки + * /api/notes/delete + */ router.post('/delete', auth, async (req, res) => { try { - + /**получение данных о заметке и удаление */ const card = tryParce(req.body.card) if (checkCard(card)) { deleteNote(card) @@ -55,32 +64,29 @@ router.post('/delete', auth, async (req, res) => { res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' }) } + /**Удаление заметки в бд */ async function deleteNote(noteToSave) { noteToSave.owner = req.user.userId const existing = await Note.findOne({ id: noteToSave.id }) if (existing) { - //console.log("EXITING"); - existing.remove() - } else res.status(500).json({ message: 'уже удален' }) } }) - - +/** + * Получение массива заметок + * /api/notes/ + */ router.get('/', auth, async (req, res) => { try { - //console.log(req.user.userId); + /**Нахождение пользовательских заметок в бд */ const notes = await Note.find({ owner: req.user.userId }) - //console.log(notes); - res.json(notes) + res.status(200).json(notes) } catch (e) { - //console.log("\n\n\n GET"); - //console.log(e); - res.status(500).json({ message: '[GET] Что-то пошло не так, попробуйте снова' }) + res.status(500).json({ message: 'Что-то пошло не так, попробуйте снова' }) } }) diff --git a/validation/CardCheck.js b/validation/CardCheck.js index 4e60bb1..c38d152 100644 --- a/validation/CardCheck.js +++ b/validation/CardCheck.js @@ -1,3 +1,8 @@ +/** + * Проверка рбьекта заметки + * @param {*} card + * @returns + */ function checkCard(card) { return ( typeof card.id === "string" &&