diff --git a/README.md b/README.md index 6041ac6..d3dee72 100644 --- a/README.md +++ b/README.md @@ -27,18 +27,18 @@ ## Live Demo -https://blogger-bhaiya.herokuapp.com +https://ok-blogger.herokuapp.com/ ## Swagger Documentation -https://blogger-bhaiya.herokuapp.com/doc/ +https://ok-blogger.herokuapp.com/doc/ ## Run on worlds fasted-server: localhost 1. Clone the repository ```sh -$ git clone https://github.com/Rajpra786/Blog-App.git +$ git clone -b development https://github.com/Rajpra786/Blog-App.git ``` 2. Install the dependencies and start the development server diff --git a/client/package.json b/client/package.json index 8e0d816..c3bd17e 100644 --- a/client/package.json +++ b/client/package.json @@ -8,7 +8,6 @@ "@mui/icons-material": "^5.0.0-rc.1", "@mui/lab": "^5.0.0-alpha.48", "@mui/material": "^5.0.0-rc.1", - "react-scripts": "^4.0.3", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", @@ -22,8 +21,10 @@ "react": "^17.0.2", "react-avatar-edit": "^1.1.0", "react-dom": "^17.0.2", + "react-infinite-scroll-component": "^6.1.0", "react-quill": "^2.0.0-beta.4", "react-router-dom": "^5.3.0", + "react-scripts": "^4.0.3", "react-slick": "^0.28.1", "web-vitals": "^1.1.2" }, @@ -50,5 +51,6 @@ "last 1 firefox version", "last 1 safari version" ] - } + }, + "proxy": "http://127.0.0.1:8081/" } diff --git a/client/src/App.css b/client/src/App.css index 74b5e05..6a5ee34 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -32,7 +32,30 @@ from { transform: rotate(0deg); } + to { transform: rotate(360deg); } } + +/* html { + overflow-x: hidden; +} + +html, +body { + color: hsl(240, 11%, 15%); + margin: 0; +} + +*:not(.material-symbols-rounded, h2.MuiTypography-root, .disableFont) { + font-family: "Outfit", sans-serif !important; +} + +.MuiTouchRipple-rippleVisible { + animation-duration: 0.25s !important; +} + +.MuiTouchRipple-child { + filter: opacity(0.4) !important; +} */ \ No newline at end of file diff --git a/client/src/App.js b/client/src/App.js index b52eefc..061af15 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -14,6 +14,7 @@ import ProfileUpdate from "./Containers/ProfileUpdate"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Footer from "./Components/Footer/Footer"; +//import './App.css' class App extends Component { constructor(props) { @@ -45,7 +46,7 @@ class App extends Component { - + diff --git a/client/src/Components/ActionButtons/ActionButtons.js b/client/src/Components/ActionButtons/ActionButtons.js new file mode 100644 index 0000000..71c8ecc --- /dev/null +++ b/client/src/Components/ActionButtons/ActionButtons.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { useTheme } from "@mui/material/styles"; +import ShareIcon from '@mui/icons-material/Share'; +import CommentIcon from '@mui/icons-material/Comment'; +import Fab from '@mui/material/Fab'; +import { Grid, Box, Tooltip, SpeedDial, SpeedDialAction } from "@mui/material"; + +import { Instagram, Facebook, Twitter, LinkedIn, Link, Grade } from '@mui/icons-material'; + +const socialMediaActions = [ + { icon: , name: 'Instagram' }, + { icon: , name: 'Facebook' }, + { icon: , name: 'Twitter' }, + { icon: , name: 'LinkedIn' }, + { icon: , name: 'Copy Link' }, +]; + +function SocialMediaDial() { + return ( + + } + > + {socialMediaActions.map((action) => ( + + ))} + + + ); +} + +const actions = [ + { + name: "Star", + icon: + }, + { + name: "Comment", + icon: + } +]; + +const ActionButtons = props => { + const theme = useTheme(); + return + { + actions.map(action => { + return + + + {action.icon} + + + + }) + } + + + + + +} + +export default ActionButtons; \ No newline at end of file diff --git a/client/src/Components/ActionButtons/index.js b/client/src/Components/ActionButtons/index.js new file mode 100644 index 0000000..d3cc681 --- /dev/null +++ b/client/src/Components/ActionButtons/index.js @@ -0,0 +1,3 @@ +import ActionButtons from './ActionButtons'; + +export default ActionButtons; diff --git a/client/src/Components/BlogsList/BlogsList.js b/client/src/Components/BlogsList/BlogsList.js index 5465d25..2bbc1fe 100644 --- a/client/src/Components/BlogsList/BlogsList.js +++ b/client/src/Components/BlogsList/BlogsList.js @@ -2,40 +2,57 @@ import React from "react"; import { Paper, Typography, Grid, Box } from "@mui/material"; import dateFormat from "dateformat"; import Get from "../../Requests/Get"; -import VerticalBlogCard from "../VerticalBlogCard/VerticalBlogCard"; import HorizontalBlogCard from "../HorizontalBlogCard/HorizontalBlogCard"; import { getTitle } from "./getTitle"; +import InfiniteScroll from "react-infinite-scroll-component"; class BlogsList extends React.Component { constructor(props) { super(props); this.state = { loaded: false, - maximumBlogs: props.maximumBlogs ? props.maximumBlogs : -1, + stepSize: 5, + hasMore: true, + lastDate: new Date().toISOString(), category: props.category, - blogs: props.maximumBlogs - ? Array.from(Array(props.maximumBlogs).keys()) - : Array.from(Array(10).keys()), + blogs: Array.from(Array(5).keys()), }; } - componentDidMount() { + getBlogs() { + console.log({ "State Details": this.state }); + if (this.state.blogs.length >= 20) { + this.setState({ hasMore: false }); + return; + } let url = this.state.category ? "/blogs/?tag=" + this.state.category : "/blogs/"; - if (this.state.maximumBlogs > 0) { - if (url === "/blogs/") { - url += "?maxcount=" + this.state.maximumBlogs; - } else url += "&maxcount=" + this.state.maximumBlogs; - } + if (url === "/blogs/") { + url += "?maxcount=" + this.state.stepSize; + } else url += "&maxcount=" + this.state.stepSize; + + url += "&lastDate=" + this.state.lastDate; Get(url) .then((res) => { - this.setState({ - blogs: res.blogs, - loaded: true, - }); + if (this.state.loaded) { + this.setState({ + blogs: [...this.state.blogs, ...res.blogs], + loaded: true, + lastDate: res.blogs?.slice(-1)[0]?.updatedAt + }); + + console.log({ "data": res.blogs, "last": res.blogs?.slice(-1), "lastDate": res.blogs?.slice(-1)[0]?.updatedAt }); + } + else { + this.setState({ + blogs: res.blogs, + loaded: true, + }); + } + console.log({ "State: ": this.state }); }) .catch((err) => { console.log("Error in BlogsList.js"); @@ -43,16 +60,17 @@ class BlogsList extends React.Component { }); } + componentDidMount() { + this.getBlogs(); + } + render() { return ( {this.state.category && ( @@ -62,42 +80,53 @@ class BlogsList extends React.Component { {/* vertical cards */} {this.props.type !== "horizontal" && ( - - + + - {!this.state.loaded && - this.state.blogs.map((value, index) => ( - - - - ))} + { this.getBlogs() }} + hasMore={this.state.hasMore} + loader={Loading...} + //height={400} + endMessage={ + + Yay! You have seen it all + + } + > + {!this.state.loaded && + this.state.blogs.map((value, index) => ( + + + + ))} - {this.state.loaded && - this.state.blogs.map((value, index) => ( - - - 50 - ? value.description.substring(0, 50) + "..." - : value.description - } - avatar={value.author?.avatar} - author={value.author.name} - date={dateFormat(value.updatedAt, "mmmm dS, yyyy")} - readTime={ - value.readTime ? value.readTime + " min" : "2 min" - } - userUrl={"/profile/" + value.author._id} - poster={value.image} - /> - - - ))} + {this.state.loaded && + this.state.blogs.map((value, index) => ( + + + 50 + ? value.description.substring(0, 50) + "..." + : value.description + } + avatar={value.author?.avatar} + author={value.author.name} + date={dateFormat(value.updatedAt, "mmmm dS, yyyy")} + readTime={ + value.readTime ? value.readTime + " min" : "2 min" + } + authorUrl={"/profile/" + value.author._id} + poster={value.image} + /> + + + ))} + @@ -119,9 +148,10 @@ class BlogsList extends React.Component { ({ - width:'40vh', +const cardStyles = (theme) => ({ + width: '12vw', overflow: 'hidden', position: 'relative', - marginLeft:'2vh', - marginRight:'1vh', - padding:'1vh', + marginLeft: '2vw', + marginRight: '1vw', + padding: '1vw', '&:after': { content: '""', position: 'absolute', - width: '30vh', - height: '30vh', + width: '10vw', + height: '10vw', background: `linear-gradient(210.04deg, ${theme.palette.warning.dark} -50.94%, rgba(144, 202, 249, 0) 83.49%)`, borderRadius: '50%', - top: '-1vh', - right: '-20vh' + top: '-1vw', + right: '-10vw' }, '&:before': { content: '""', position: 'absolute', - width: '30vh', - height: '30vh', + width: '10vw', + height: '10vw', background: `linear-gradient(140.9deg, ${theme.palette.warning.dark} -14.02%, rgba(144, 202, 249, 0) 70.50%)`, borderRadius: '50%', - top: '-20vh', - right: '-15vh' + top: '-20vw', + right: '-15vw' }, - border: '0.3vh solid #ddd', - borderRadius: '2vh', - '&:hover':{ - boxShadow: '0 0 3vh 0px rgba(0, 0, 0, 0.4)', + border: '0.3vw solid #ddd', + borderRadius: '2vw', + '&:hover': { + boxShadow: '0 0 3vw 0px rgba(0, 0, 0, 0.4)', transform: 'scale(1.04)' }, - '&:active':{ - transform: 'scale(1.01) translateY(0.5vh)' + '&:active': { + transform: 'scale(1.01) translateY(0.5vw)' } }); const CategoryCard = props => { const theme = useTheme(); - return + return - {props.icon} + {props.icon} @@ -65,7 +65,7 @@ const CategoryCard = props => { - + } diff --git a/client/src/Components/Footer/Footer.js b/client/src/Components/Footer/Footer.js index 974a735..7ed61e2 100644 --- a/client/src/Components/Footer/Footer.js +++ b/client/src/Components/Footer/Footer.js @@ -1,17 +1,178 @@ import React from 'react'; -import { Paper,Typography } from '@mui/material'; +import { Box, Grid, Typography } from '@mui/material'; +import MuiLink from "@mui/material/Link"; const Footer = props => { + return ( + + + + + Smartlist + + + We're a philanthropic nonprofit striving to help everyone{" "} + + ❤️ + + + + + Proudly made in the USA + + 🇺🇸 + + + + + + + + Apps + + + Smartlist + + + Smartlist Availability + + + Smartlist Recipe Generator + + + Smartlist Dressing + + + Smartlist Collaborate + + + + + Company + + + Join our team + + + Terms of service + + + Privacy policy + + + Our team + - return( - - ) + + Links + + + Contact us + + + Knowledge base + + + hello@smartlist.tech + + + Official Discord + + + GitHub + + + + + Sponsors + + + + + + + + + + ); }; diff --git a/client/src/Components/HorizontalBlogCard/HorizontalBlogCard.js b/client/src/Components/HorizontalBlogCard/HorizontalBlogCard.js index 450448b..44bbbb9 100644 --- a/client/src/Components/HorizontalBlogCard/HorizontalBlogCard.js +++ b/client/src/Components/HorizontalBlogCard/HorizontalBlogCard.js @@ -10,23 +10,16 @@ const CardStyle = { paddingLeft: "1vw", paddingRight: "1vw", paddingBottom: "0vh", - width: 550, - height: 250, + width: "100%", + height: 200, overflow: "hidden", position: "relative", - border: "hidden", + border: "none", + boxShadow: "none", borderRadius: "2vh", - "&:hover": { - boxShadow: "0 1vh 2vh 0px rgba(0, 0, 0, 0.4)", - transform: "scale(1.04)", - }, "&:active": { transform: "scale(1.01) translateY(0.5vh)", - }, - "@media (max-width:780px)": { - width: 370, - height: 180, - }, + } }; const HorizontalBlogCard = (props) => { @@ -37,23 +30,9 @@ const HorizontalBlogCard = (props) => { to={props.url} style={{ textDecoration: "none", color: theme.palette.text.primary }}> - - - - {props.title} + + + {props.title} { style={{ textDecoration: "none", color: theme.palette.text.primary, + bottom: "3px", + position: "absolute" }}> { readTime={props.readTime}> + + {/* + + */} ); diff --git a/client/src/Components/HorizontalBlogCard/UserDetails.js b/client/src/Components/HorizontalBlogCard/UserDetails.js index e411865..3be83e4 100644 --- a/client/src/Components/HorizontalBlogCard/UserDetails.js +++ b/client/src/Components/HorizontalBlogCard/UserDetails.js @@ -14,7 +14,7 @@ export function UserDetails(props) { }}> } - title={props.author} + title={{props.author}} subheader={ - {props.date} + {props.date} { + const [open, setOpen] = React.useState(true); + + return ( + <> + + + { + setOpen(false); + }} + > + + + } + severity="info" + sx={{ + background: "linear-gradient(to right, #237A57, #093028)", + width: "100%", + borderRadius: 4, + boxSizing: "border-box", + zIndex: 1 + }} + variant="filled" + > + + {props.msg} + + + + + > + ) +}; + +export default InfoMsg; diff --git a/client/src/Components/InfoMsg/InfoMsg.lazy.js b/client/src/Components/InfoMsg/InfoMsg.lazy.js new file mode 100644 index 0000000..501d126 --- /dev/null +++ b/client/src/Components/InfoMsg/InfoMsg.lazy.js @@ -0,0 +1,11 @@ +import React, { lazy, Suspense } from 'react'; + +const LazyInfoMsg = lazy(() => import('./InfoMsg')); + +const InfoMsg = props => ( + + + +); + +export default InfoMsg; diff --git a/client/src/Components/InfoMsg/InfoMsg.stories.js b/client/src/Components/InfoMsg/InfoMsg.stories.js new file mode 100644 index 0000000..8641e83 --- /dev/null +++ b/client/src/Components/InfoMsg/InfoMsg.stories.js @@ -0,0 +1,12 @@ +/* eslint-disable */ +import InfoMsg from './InfoMsg'; + +export default { + title: "InfoMsg", +}; + +export const Default = () => ; + +Default.story = { + name: 'default', +}; diff --git a/client/src/Components/InfoMsg/InfoMsg.test.js b/client/src/Components/InfoMsg/InfoMsg.test.js new file mode 100644 index 0000000..b5a1bb8 --- /dev/null +++ b/client/src/Components/InfoMsg/InfoMsg.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import InfoMsg from './InfoMsg'; + +it('It should mount', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); \ No newline at end of file diff --git a/client/src/Components/Login/LoginPage.js b/client/src/Components/Login/LoginPage.js index daceca4..3571899 100644 --- a/client/src/Components/Login/LoginPage.js +++ b/client/src/Components/Login/LoginPage.js @@ -48,7 +48,6 @@ const LoginPage = (props) => { fullWidth sx={{ m: 1, - "& .MuiTextField-root": { margin: theme.spacing(1), }, diff --git a/client/src/Components/MiniBlogCard/MiniBlogCard.js b/client/src/Components/MiniBlogCard/MiniBlogCard.js index 70df870..d7a7f40 100644 --- a/client/src/Components/MiniBlogCard/MiniBlogCard.js +++ b/client/src/Components/MiniBlogCard/MiniBlogCard.js @@ -1,75 +1,74 @@ import * as React from "react"; import { useTheme } from "@mui/material/styles"; import { - Card, - CardContent, - CardHeader, Box, - CardMedia, Typography, + ListItem, + ListItemAvatar, + Avatar, + ListItemText } from "@mui/material"; import { Link, withRouter } from "react-router-dom"; -const cardStyle = { - display: "flex", - width: "23vw", - height: "27vh", - margin: "1vw", - overflow: "hidden", - border: "0.3vh solid #ddd", - borderRadius: "2vh", - "&:hover": { - boxShadow: "0 1vh 2vh 0px rgba(0, 0, 0, 0.4)", - transform: "scale(1.04)", - }, - "&:active": { - transform: "scale(1.01) translateY(0.5vh)", - }, -}; - const MiniBlogCard = (props) => { const theme = useTheme(); return ( - - - - - - {props.title} - - - - {props.author}} - subheader={ - - {props.date} - - } - /> - - - - - + + + + + + {props.author} + + + + } + secondary={ + + + + {props.title} + + + + } + /> + + + + + + + ); }; diff --git a/client/src/Components/NavBar/NavBar.js b/client/src/Components/NavBar/NavBar.js index 7ad0f42..8b9d314 100644 --- a/client/src/Components/NavBar/NavBar.js +++ b/client/src/Components/NavBar/NavBar.js @@ -9,6 +9,8 @@ import MenuIcon from "@mui/icons-material/Menu"; import Login from "../Login/Login"; import { Link } from "react-router-dom"; import { UserContext } from "../../Context/UserContext"; +import InfoMsg from "../InfoMsg/InfoMsg"; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import { AppBar, @@ -19,12 +21,97 @@ import { CssBaseline, Drawer, Modal, + Button, } from "@mui/material"; import DrawerWindow from "./DrawerWindow"; -import { MenuBar } from "./MenuBar"; +import { styled, alpha } from '@mui/material/styles'; +import InputBase from '@mui/material/InputBase'; +import SearchIcon from '@mui/icons-material/Search'; + +const Search = styled('div')(({ theme }) => ({ + position: 'relative', + borderRadius: theme.shape.borderRadius, + backgroundColor: alpha(theme.palette.common.white, 0.15), + '&:hover': { + backgroundColor: alpha(theme.palette.common.white, 0.25), + }, + marginLeft: 0, + width: '100%', + [theme.breakpoints.up('sm')]: { + marginLeft: theme.spacing(1), + width: 'auto', + }, +})); + +const SearchIconWrapper = styled('div')(({ theme }) => ({ + padding: theme.spacing(0, 2), + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +const StyledInputBase = styled(InputBase)(({ theme }) => ({ + color: 'inherit', + '& .MuiInputBase-input': { + padding: theme.spacing(1, 1, 1, 0), + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)})`, + transition: theme.transitions.create('width'), + width: '100%', + [theme.breakpoints.up('sm')]: { + width: '12ch', + '&:focus': { + width: '20ch', + }, + }, + }, +})); const drawerWidth = 240; +const styles = { + button: { textTransform: "none", borderRadius: 4, px: 2 } +}; + +const NavLink = (props) => { + console.log(props.theme) + return ( + + + + {props.name} + + + + ) +} + +const navLinks = [ + { + link: "/our-team", + name: "Our Team" + }, + { + link: "/write", + name: "Write" + } +] + const NavBar = (props) => { const { window } = props; const [mobileOpen, setMobileOpen] = React.useState(false); @@ -71,7 +158,20 @@ const NavBar = (props) => { - + { sx={{ display: { md: "none", xs: "block" } }}> - - + + + Blogger Bhaiya - - + + + + { + navLinks.map((val, index) => { + return + }) + } + + + + + Continue to my account + + + Sign in + + + + + + + + + + + { )} - + handleClose={handleClose}> */} + - - - Recommendations + + + More from Ok-Blogger - {this.state.blogs.map((blog) => { - return ( - - ); - })} + + {this.state.blogs.map((blog) => { + return ( + + + + + ); + })} + ); } diff --git a/client/src/Components/Tags/Tags.js b/client/src/Components/Tags/Tags.js new file mode 100644 index 0000000..64f6134 --- /dev/null +++ b/client/src/Components/Tags/Tags.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { useTheme } from "@mui/material/styles"; +import { Box, Chip } from '@mui/material'; + +const Tags = props => { + const theme = useTheme(); + + return + { + props.tags.map((val, index) => { + return + }) + } + +} + +export default Tags; diff --git a/client/src/Components/Tags/index.js b/client/src/Components/Tags/index.js new file mode 100644 index 0000000..8c9d0a5 --- /dev/null +++ b/client/src/Components/Tags/index.js @@ -0,0 +1,3 @@ +import Tags from './Tags'; + +export default Tags; diff --git a/client/src/Components/UserCard/UserCard.js b/client/src/Components/UserCard/UserCard.js index 3029da3..827f632 100644 --- a/client/src/Components/UserCard/UserCard.js +++ b/client/src/Components/UserCard/UserCard.js @@ -1,7 +1,6 @@ import React from "react"; import Get from "../../Requests/Get"; -import { Typography, Avatar, Card, CardContent, Divider } from "@mui/material"; -import { CardBottom } from "./CardBottom"; +import { Typography, Avatar, CardContent, Button, Grid, Paper, Divider } from "@mui/material"; class UserCard extends React.Component { constructor(props) { @@ -41,32 +40,36 @@ class UserCard extends React.Component { render() { return ( - - - - - {this.state.name} - + + + + + + {this.state.name} + - - {this.state.description} - - - - - + + {this.state.description} + + + + FOLLOW + + + ); } } diff --git a/client/src/Containers/BlogById/BlogById.js b/client/src/Containers/BlogById/BlogById.js index f4f78be..e7dac5e 100644 --- a/client/src/Containers/BlogById/BlogById.js +++ b/client/src/Containers/BlogById/BlogById.js @@ -8,6 +8,7 @@ import { BlogHeader } from "./BlogHeader"; import { Poster } from "./Poster"; import { BlogContent } from "./BlogContent"; import { RightPanel } from "./RightPanel"; +import ActionButtons from "./../../Components/ActionButtons"; class BlogById extends React.Component { constructor(props) { @@ -23,12 +24,19 @@ class BlogById extends React.Component { tags: [], lastUpdated: "", readTime: "", + atBottom: false, }; } static contextType = UserContext; - componentDidMount() { - Get("/blogs/" + this.props.match.params.id) + componentWillReceiveProps(nextProps) { + if (nextProps.location.state === 'desiredState' && nextProps.match.params.id !== this.props.match.params.id) { + this.updateData(nextProps.match.params.id); + } + } + + updateData(blogId) { + Get("/blogs/" + blogId) .then((res) => { this.setState({ title: res.data.title, @@ -56,12 +64,15 @@ class BlogById extends React.Component { } }); } + componentDidMount() { + this.updateData(this.props.match.params.id); + } render() { const { isAuth, userId } = this.context; return ( - + - - + + + ); diff --git a/client/src/Containers/BlogById/BlogContent.js b/client/src/Containers/BlogById/BlogContent.js index 83d10d9..767b60f 100644 --- a/client/src/Containers/BlogById/BlogContent.js +++ b/client/src/Containers/BlogById/BlogContent.js @@ -1,23 +1,28 @@ import React from "react"; -import { Paper } from "@mui/material"; +import { Grid, Paper } from "@mui/material"; import { parser } from "./parser"; +import UserCard from "../../Components/UserCard"; export function BlogContent(props) { return ( - - {parser(props.content)} - + + + {parser(props.content)} + + {props.author && } + ); } diff --git a/client/src/Containers/BlogById/BlogHeader.js b/client/src/Containers/BlogById/BlogHeader.js index 5507170..b26cef2 100644 --- a/client/src/Containers/BlogById/BlogHeader.js +++ b/client/src/Containers/BlogById/BlogHeader.js @@ -1,51 +1,34 @@ import React from "react"; import { Paper, Typography } from "@mui/material"; import AccessTimeIcon from "@mui/icons-material/AccessTime"; -import { EditButton } from "../../Components/Buttons/Buttons"; export function BlogHeader(props) { return ( - + elevation={0} + align="center" + sx={{ mb: "1vw" }} + > + {props.title} - {props.isAuth && props.userId === props.author && ( - - )} By {props.authorName} ⚫ {props.lastUpdated}{" "} {" "} diff --git a/client/src/Containers/BlogById/Poster.js b/client/src/Containers/BlogById/Poster.js index 55fe1e7..2bcc2bc 100644 --- a/client/src/Containers/BlogById/Poster.js +++ b/client/src/Containers/BlogById/Poster.js @@ -4,6 +4,7 @@ import { Paper } from "@mui/material"; export function Poster(props) { return ( - - {props.author && } - - + + ); } diff --git a/client/src/Containers/Home/Home.js b/client/src/Containers/Home/Home.js index 5839897..250b1f4 100644 --- a/client/src/Containers/Home/Home.js +++ b/client/src/Containers/Home/Home.js @@ -1,28 +1,39 @@ import React from 'react'; -import CategoryBar from '../../Components/CategoryBar'; -import HomeCarousel from '../../Components/HomeCarousel/HomeCarousel'; import BlogsList from '../../Components/BlogsList/BlogsList'; -import Categories from '../../Categories'; -import EditorsPickBlock from '../../Components/EditorsPick/EditorsPickBlock/EditorsPickBlock'; +import { Box, Grid } from '@mui/material'; +import Tags from '../../Components/Tags'; + +const tags = [ + { name: "Ok bro", link: "/bro" }, + { name: "Ok", link: "/bro" }, + { name: "Ok bro2", link: "/bro" }, + { name: "Ok bro3", link: "/bro" }, + { name: "Ok bro4", link: "/bro" }, + { name: "Ok", link: "/bro" }, + { name: "Ok bro2", link: "/bro" }, + { name: "Ok bro3", link: "/bro" }, + { name: "Ok bro4", link: "/bro" }, + { name: "Ok bro2", link: "/bro" }, + { name: "Ok bro3", link: "/bro" }, + { name: "Ok bro4", link: "/bro" }, + { name: "Ok", link: "/bro" }, + { name: "Ok bro2", link: "/bro" }, + { name: "Ok bro3", link: "/bro" }, + { name: "Ok bro4", link: "/bro" }, +] const Home = props => { - const data = [1,2]; return ( - - - - {Categories.map((value,index)=>{ - if(index % 2 === 0){ - return ; - } - else - return ; - })} - + + + + + + + + - - ) }; export default Home; diff --git a/client/src/Requests/ImageUpload.js b/client/src/Requests/ImageUpload.js index 3bbb3c0..a3d128c 100644 --- a/client/src/Requests/ImageUpload.js +++ b/client/src/Requests/ImageUpload.js @@ -6,6 +6,8 @@ const ImageUpload = (file) => { reject("File type is not supported!"); } + //compress image + //get url to upload image from s3 fetch("/api/blogs/image/new/" + file.name) .then(res => res.json()) diff --git a/client/src/themes/dark.js b/client/src/themes/dark.js index 527e817..40b2d1b 100644 --- a/client/src/themes/dark.js +++ b/client/src/themes/dark.js @@ -1,14 +1,15 @@ -import {createTheme } from '@mui/material/styles'; +import { createTheme } from '@mui/material/styles'; const darkTheme = createTheme({ palette: { - mode: 'dark', + mode: 'dark', + navBack: "rgba(28, 25, 27, 0.8)" }, typography: { fontFamily: [ - 'Serif' + 'sans-serif' ].join(','), - } + } }); export default darkTheme; \ No newline at end of file diff --git a/client/src/themes/light.js b/client/src/themes/light.js index 5ea1767..5414c4b 100644 --- a/client/src/themes/light.js +++ b/client/src/themes/light.js @@ -1,14 +1,15 @@ -import {createTheme } from '@mui/material/styles'; +import { createTheme } from '@mui/material/styles'; const lightTheme = createTheme({ palette: { - mode: 'light', + mode: 'light', + navBack: "rgba(250,250,250,.9)" }, typography: { fontFamily: [ - 'Serif' + 'Serif' ].join(','), - } + } }); export default lightTheme; \ No newline at end of file diff --git a/controllers/appControllers/getHome.js b/controllers/appControllers/getHome.js new file mode 100644 index 0000000..82a4fff --- /dev/null +++ b/controllers/appControllers/getHome.js @@ -0,0 +1,14 @@ +const { Blog } = require("../../models/blog"); +const { Comments } = require("../../models/comments"); +const { User } = require("../../models/user"); +const { Stats } = require("../../models/stats"); + + +module.exports = async (req, res) => { + //1. top 10 of ok-blogger + //2. latest 10 of ok-blogger + // list of to 10 categories + // Should we implement infinite scrolling : Yes + + +} \ No newline at end of file diff --git a/controllers/appControllers/getPost.js b/controllers/appControllers/getPost.js new file mode 100644 index 0000000..4b23702 --- /dev/null +++ b/controllers/appControllers/getPost.js @@ -0,0 +1,99 @@ +const { Blog } = require("../../models/blog"); + +// async function transform() { +// await Blog.find().then(async blogs => { +// for (let blog of blogs) { +// if (blog._id.toString() !== "6159fde88150df0056cf2df6") { +// let stat = new Stats({ +// blogId: blog._id +// }) +// await stat.save(async (err, cmt) => { +// blog.stats = cmt._id; +// await blog.save().then(blog => { +// console.log("added to ", blog._id); +// }) +// }) +// } +// else { +// console.log("Hello ", blog._id); +// } +// } +// }).catch(err => { +// console.log(err); +// }) +// } + +// how to populate multiple ref objects in mongodb? +async function getBlogDetails(blogId, result) { + await Blog + .findById(blogId) + .populate({ + path: "author", + select: "name email description github twitter website avatar blogs", + populate: { + path: "blogs", + select: "title description image readTime tags stats", + options: { + limit: 3, + sort: { createdAt: -1 }, + }, + populate: { + path: "stats", + select: "approvals reads views likes" + } + } + }) + .populate({ + path: "stats", + select: "approvals reads views likes" + }) + .then(data => { + let dataNew = JSON.parse(JSON.stringify(data)); + let { author, ...blogData } = dataNew; + let { blogs, ...AuthorData } = author; + + result.fromAuthor = blogs; + result.author = AuthorData; + result.blog = blogData; + }).catch(err => { + console.log(JSON.stringify(err, Object.getOwnPropertyNames(err), 4)); + result.blog = null; + throw err; + // TODO : log error here + }); +} + +module.exports = async (req, res) => { + /* + #swagger.tags = ['App'] + #swagger.summary = 'Get Post By Id' + #swagger.description = 'Endpoint get a blog and its related components by blogId. It provides Blog details, its author details, recommandation, related posts' + + #swagger.security = [{ + "apiKeyAuth": [] + }] + */ + + // Blog, Author, Releted posts, More from Author, Top from ok-blogger + + const result = {}; + + const listPromise = []; + // Get blog and author details + listPromise.push(getBlogDetails(req.params.id, result)); + + // Get top from ok-blogger + + await Promise.all(listPromise).then(data => { + return res.status(200).json({ + success: true, + message: "Request completed", + data: result, + }); + }).catch(err => { + return res.status(503).json({ + success: false, + message: "blog not found!", + }); + }) +}; diff --git a/controllers/blogControllers/getBlogs.js b/controllers/blogControllers/getBlogs.js index 082f9e3..27df214 100644 --- a/controllers/blogControllers/getBlogs.js +++ b/controllers/blogControllers/getBlogs.js @@ -4,36 +4,36 @@ module.exports = async (req, res) => { /* #swagger.tags = ['Blog'] #swagger.summary = 'Get All blogs' - #swagger.description = 'Endpoint to get all blogs based on query parameters' + #swagger.description = 'Endpoint to get all blogs based on query parameters' #swagger.parameters['tag'] = { - in: 'query', - description: 'blog tag', - schema: { + in: 'query', + description: 'blog tag', + schema: { $tag:'science-and-space' - } - } + } + } #swagger.parameters['max count'] = { - in: 'query', - description: 'Maximum number of elements that need to be returned', - schema: { + in: 'query', + description: 'Maximum number of elements that need to be returned', + schema: { $maxcount:3 - } - } + } + } #swagger.parameters['sort'] = { - in: 'query', - description: 'Sort or Not sorted', - schema: { + in: 'query', + description: 'Sort or Not sorted', + schema: { $sort:true - } - } + } + } #swagger.security = [{ - "apiKeyAuth": [] - }] + "apiKeyAuth": [] + }] */ var blogs = []; @@ -44,12 +44,17 @@ module.exports = async (req, res) => { } if (req.query.sort) { - filters.sort = { updatedAt: "asc" }; + filters.sort = { updatedAt: -1 }; } if (req.query.tag) { query.tags = { $in: req.query.tag }; } + + if (req.query.lastDate) { + query.updatedAt = { $lt: new Date(req.query.lastDate) } + } + await Blog.find( query, "author readTime title description image tags updatedAt", diff --git a/controllers/commentControllers/commentRoutes.js b/controllers/commentControllers/commentRoutes.js deleted file mode 100644 index 1fdd0dd..0000000 --- a/controllers/commentControllers/commentRoutes.js +++ /dev/null @@ -1,12 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const auth = require("../../middleware/auth"); -const addComment = require("./addComment"); -const updateComment = require("./updateComment"); -const getComments = require("./getComments"); - -router.get("/:commentBoxId", getComments); -router.post("/:commentBoxId/new", auth, addComment); -router.put("/:commentBoxId/:commentId", auth, updateComment); - -module.exports = router; diff --git a/controllers/userControllers/userRoutes.js b/controllers/userControllers/userRoutes.js deleted file mode 100644 index f5f4ed3..0000000 --- a/controllers/userControllers/userRoutes.js +++ /dev/null @@ -1,20 +0,0 @@ -var router = require("express").Router(); -const auth = require("../../middleware/auth"); -const login = require("./login"); -const getMyProfile = require("./getMyProfile"); -const getPublicProfile = require("./getPublicProfile"); -const logout = require("./logout"); -const register = require("./register"); -const updateProfile = require("./updateProfile"); - -//session -router.post("/session", login); -router.delete("/session", logout); - -//for users -router.post("/new", register); -router.get("/my", auth, getMyProfile); -router.get("/:id", getPublicProfile); -router.put("/:id", auth, updateProfile); - -module.exports = router; diff --git a/endpoints.js b/endpoints.js deleted file mode 100644 index 625cfa1..0000000 --- a/endpoints.js +++ /dev/null @@ -1,12 +0,0 @@ -const express = require("express"); -const userRoutes = require("./controllers/userControllers/userRoutes"); -const blogRoutes = require("./controllers/blogControllers/blogRoutes"); -const commentRoutes = require("./controllers/commentControllers/commentRoutes"); - -const router = express.Router(); - -router.use("/api/users/", userRoutes); -router.use("/api/blogs/", blogRoutes); -router.use("/api/comments/", commentRoutes); - -module.exports = router; diff --git a/index.js b/index.js index 8ff374b..d0c3b7a 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ const expressSession = require("express-session"); const morgan = require("morgan"); const path = require("path"); -const endpoints = require("./endpoints"); +const endpoints = require("./routes/endpoints"); const swaggerUi = require("swagger-ui-express"); const swaggerFile = require("./swagger_output.json"); @@ -67,10 +67,10 @@ var options = { app.use(endpoints); app.use("/doc", swaggerUi.serve, swaggerUi.setup(swaggerFile, options)); -app.use(express.static(path.join(__dirname, "client/build"))); -app.get("*", (req, res) => { - res.sendFile(path.join(__dirname + "/client/build/index.html")); -}); +// app.use(express.static(path.join(__dirname, "client/build"))); +// app.get("*", (req, res) => { +// res.sendFile(path.join(__dirname + "/client/build/index.html")); +// }); // Start Server app.listen(PORT, () => { diff --git a/middleware/isAuth.js b/middleware/isAuth.js new file mode 100644 index 0000000..0c057dc --- /dev/null +++ b/middleware/isAuth.js @@ -0,0 +1,13 @@ +const { User } = require('../models/user'); +require("dotenv").config(); + +module.exports = (req, res, next) => { + //parse the token + // let token = req.headers.cookies.replace("authToken=","").replace("%22","").replace("%22",""); + + User.findById(req.session.userId, (err, user) => { + if (err) throw err; + req.user = user; + next(); + }); +} \ No newline at end of file diff --git a/middleware/isAuther.js b/middleware/isAuther.js deleted file mode 100644 index e69de29..0000000 diff --git a/models/blog.js b/models/blog.js index 769982c..790ffa7 100644 --- a/models/blog.js +++ b/models/blog.js @@ -36,6 +36,10 @@ const blogSchema = mongoose.Schema( type: mongoose.Schema.Types.ObjectId, ref: "Comments", }, + stats: { + type: mongoose.Schema.Types.ObjectId, + ref: "Stats", + }, tags: [ { type: String, diff --git a/models/stats.js b/models/stats.js new file mode 100644 index 0000000..f0485c9 --- /dev/null +++ b/models/stats.js @@ -0,0 +1,37 @@ +const mongoose = require("mongoose"); + +require("dotenv").config(); + +const statsSchema = mongoose.Schema( + { + blogId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Blog", + required: [true, "Stats should be associated with blogId"], + }, + likedBy: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], + verfiedBy: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], + approvals: { + type: Number, + default: 0, + }, + reads: { + type: Number, + default: 0, + }, + views: { + type: Number, + default: 0, + }, + likes: { + type: Number, + default: 0, + } + }, + { + timestamps: true, + } +); + +const Stats = mongoose.model("Stats", statsSchema); +module.exports = { Stats }; diff --git a/package.json b/package.json index 196aa4a..2c7f340 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "homepage": "https://github.com/Rajpra786/Blog-App#readme", "dependencies": { + "@types/cookie": "^0.4.0", "aws-sdk": "^2.983.0", "bcrypt": "^5.0.1", "body-parser": "^1.19.0", @@ -30,15 +31,12 @@ "dotenv": "^10.0.0", "express": "^4.17.1", "express-session": "^1.17.2", - "mongoose": "^5.11.13", + "mongoose": "^5.13.15", "morgan": "^1.10.0", "multer": "^1.4.3", "multer-s3": "^2.9.1", - "swagger-ui-express": "^4.1.6", - "@types/cookie": "^0.4.0", - "swagger-autogen": "^2.11.2" - }, - "devDependencies": { - + "nodemon": "^2.0.20", + "swagger-autogen": "^2.11.2", + "swagger-ui-express": "^4.1.6" } } diff --git a/routes/appRoutes.js b/routes/appRoutes.js new file mode 100644 index 0000000..cc8f29c --- /dev/null +++ b/routes/appRoutes.js @@ -0,0 +1,9 @@ +var router = require("express").Router(); +const isAuth = require("../middleware/isAuth"); +const getPost = require("../controllers/appControllers/getPost"); + +// Get /api/app/:id Get all for a given blog id + +router.get("/:id", isAuth, getPost); + +module.exports = router; diff --git a/controllers/blogControllers/blogRoutes.js b/routes/blogRoutes.js similarity index 51% rename from controllers/blogControllers/blogRoutes.js rename to routes/blogRoutes.js index 252b5f4..4a867f0 100644 --- a/controllers/blogControllers/blogRoutes.js +++ b/routes/blogRoutes.js @@ -1,10 +1,10 @@ var router = require("express").Router(); -const auth = require("../../middleware/auth"); -const createBlog = require("./createBlog"); -const getBlogById = require("./getBlogById"); -const getBlogs = require("./getBlogs"); -const updateBlog = require("./updateBlog"); -const getUploadUrl = require("./getUploadUrl"); +const auth = require("../middleware/auth"); +const createBlog = require("../controllers/blogControllers/createBlog"); +const getBlogById = require("../controllers/blogControllers/getBlogById"); +const getBlogs = require("../controllers/blogControllers/getBlogs"); +const updateBlog = require("../controllers/blogControllers/updateBlog"); +const getUploadUrl = require("../controllers/blogControllers/getUploadUrl"); // POST /api/blogs/new (Auth) Create a new blog // PUT /api/blogs/:id (Auth) Update a blog diff --git a/routes/commentRoutes.js b/routes/commentRoutes.js new file mode 100644 index 0000000..62bb482 --- /dev/null +++ b/routes/commentRoutes.js @@ -0,0 +1,12 @@ +const express = require("express"); +const router = express.Router(); +const auth = require("../middleware/auth"); +const addComment = require("../controllers/commentControllers/addComment"); +const updateComment = require("../controllers/commentControllers/updateComment"); +const getComments = require("../controllers/commentControllers/getComments"); + +router.get("/:commentBoxId", getComments); +router.post("/:commentBoxId/new", auth, addComment); +router.put("/:commentBoxId/:commentId", auth, updateComment); + +module.exports = router; diff --git a/routes/endpoints.js b/routes/endpoints.js new file mode 100644 index 0000000..4c3f033 --- /dev/null +++ b/routes/endpoints.js @@ -0,0 +1,13 @@ +const express = require("express"); +const userRoutes = require("./userRoutes"); +const blogRoutes = require("./blogRoutes"); +const commentRoutes = require("./commentRoutes"); +const appRoutes = require("./appRoutes"); + +const router = express.Router(); + +router.use("/api/users/", userRoutes); +router.use("/api/blogs/", blogRoutes); +router.use("/api/comments/", commentRoutes); +router.use("/api/app/", appRoutes) +module.exports = router; diff --git a/routes/userRoutes.js b/routes/userRoutes.js new file mode 100644 index 0000000..c3e3632 --- /dev/null +++ b/routes/userRoutes.js @@ -0,0 +1,20 @@ +var router = require("express").Router(); +const auth = require("../middleware/auth"); +const login = require("../controllers/userControllers/login"); +const getMyProfile = require("../controllers/userControllers/getMyProfile"); +const getPublicProfile = require("../controllers/userControllers/getPublicProfile"); +const logout = require("../controllers/userControllers/logout"); +const register = require("../controllers/userControllers/register"); +const updateProfile = require("../controllers/userControllers/updateProfile"); + +//session +router.post("/session", login); +router.delete("/session", logout); + +//for users +router.post("/new", register); +router.get("/my", auth, getMyProfile); +router.get("/:id", getPublicProfile); +router.put("/:id", auth, updateProfile); + +module.exports = router; diff --git a/swagger.js b/swagger.js index f96d306..10aa1cc 100644 --- a/swagger.js +++ b/swagger.js @@ -56,7 +56,7 @@ const doc = { }; const outputFile = "./swagger_output.json"; -const endpointsFiles = ["./endpoints.js"]; +const endpointsFiles = ["./routes/endpoints.js"]; swaggerAutogen(outputFile, endpointsFiles, doc); // swaggerAutogen(outputFile, endpointsFiles, doc).then(() => { diff --git a/swagger_output.json b/swagger_output.json index 8c80d13..bcbd3bf 100644 --- a/swagger_output.json +++ b/swagger_output.json @@ -2,14 +2,13 @@ "swagger": "2.0", "info": { "version": "1.0.0", - "title": "Blog API", + "title": "Blogger-Bhaiya backend API", "description": "This is the documentation for backend server API." }, - "host": "localhost:8081", + "host": "blogger-bhaiya.herokuapp.com", "basePath": "/", - "tags": [], "schemes": [ - "http" + "https" ], "securityDefinitions": { "apiKeyAuth": { @@ -44,11 +43,11 @@ "properties": { "email": { "type": "string", - "example": "hello@gmail.com" + "example": "hello2@gmail.com" }, "password": { "type": "string", - "example": "12345678" + "example": "87654321" } }, "required": [ @@ -298,6 +297,11 @@ "name": "maxcount", "in": "query", "type": "string" + }, + { + "name": "lastDate", + "in": "query", + "type": "string" } ], "responses": { @@ -578,6 +582,36 @@ } ] } + }, + "/api/app/{id}": { + "get": { + "tags": [ + "App" + ], + "summary": "Get Post By Id", + "description": "Endpoint get a blog and its related components by blogId. It provides Blog details, its author details, recommandation, related posts", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "503": { + "description": "Service Unavailable" + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } } }, "definitions": {
+ Yay! You have seen it all +