[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\"@babel/preset-env\",\n      {\n        \"targets\": {\n          \"node\": \"current\"\n        }\n      }\n    ],\n    \"@babel/preset-react\"\n  ],\n  \"plugins\": [\n    \"react-hot-loader/babel\"\n  ]\n}\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 7\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n  - security\n# Label to use when marking an issue as stale\nstaleLabel: inactive\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".gitignore",
    "content": "/node_modules/\n/dist/\n/data/\nnpm-debug.log\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2018 Shama Hoque\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# MERN Mediastream 2.0\n- *Looking for the first edition code? [Check here](https://github.com/shamahoque/mern-mediastream/tree/master)*\n\nA media streaming application with media upload and stream features - developed using React, Node, Express and MongoDB.\n\n![MERN Mediastream](https://s3.amazonaws.com/mernbook/git+/mediastream.png \"MERN Mediastream\")\n\n### [Live Demo](http://mediastream2.mernbook.com/ \"MERN Mediastream\")\n\n#### What you need to run this code\n1. Node (13.12.0)\n2. NPM (6.14.4) or Yarn (1.22.4)\n3. MongoDB (4.2.0)\n\n####  How to run this code\n1. Clone this repository\n2. Open command line in the cloned folder,\n   - To install dependencies, run ```  npm install  ``` or ``` yarn ```\n   - To run the application for development, run ```  npm run development  ``` or ``` yarn development ```\n4. Open [localhost:3000](http://localhost:3000/) in the browser\n----\n### More applications built using this stack\n\n* [MERN Skeleton](https://github.com/shamahoque/mern-social/tree/second-edition)\n* [MERN Social](https://github.com/shamahoque/mern-social/tree/second-edition)\n* [MERN Classroom](https://github.com/shamahoque/mern-classroom)\n* [MERN Marketplace](https://github.com/shamahoque/mern-marketplace/tree/second-edition)\n* [MERN Expense Tracker](https://github.com/shamahoque/mern-expense-tracker)\n* [MERN VR Game](https://github.com/shamahoque/mern-vrgame/tree/second-edition)\n\nLearn more at [mernbook.com](http://www.mernbook.com/)\n\n----\n## Get the book\n#### [Full-Stack React Projects - Second Edition](https://www.packtpub.com/web-development/full-stack-react-projects-second-edition)\n*Learn MERN stack development by building modern web apps using MongoDB, Express, React, and Node.js*\n\n<a href=\"https://www.packtpub.com/web-development/full-stack-react-projects-second-edition\"><img src=\"https://mernbook.s3.amazonaws.com/git+/Book_2Ed.jpg\" align=\"center\" width=\"400\" alt=\"Full-Stack React Projects\"></a>\n\nReact combined with industry-tested, server-side technologies, such as Node, Express, and MongoDB, enables you to develop and deploy robust real-world full-stack web apps. This updated second edition focuses on the latest versions and conventions of the technologies in this stack, along with their new features such as Hooks in React and async/await in JavaScript. The book also explores advanced topics such as implementing real-time bidding, a web-based classroom app, and data visualization in an expense tracking app.\n\nFull-Stack React Projects will take you through the process of preparing the development environment for MERN stack-based web development, creating a basic skeleton app, and extending it to build six different web apps. You'll build apps for social media, classrooms, media streaming, online marketplaces with real-time bidding, and web-based games with virtual reality features. Throughout the book, you'll learn how MERN stack web development works, extend its capabilities for complex features, and gain actionable insights into creating MERN-based apps, along with exploring industry best practices to meet the ever-increasing demands of the real world.\n\nThings you'll learn in this book:\n\n- Extend a MERN-based application to build a variety of applications\n- Add real-time communication capabilities with Socket.IO\n- Implement data visualization features for React applications using Victory\n- Develop media streaming applications using MongoDB GridFS\n- Improve SEO for your MERN apps by implementing server-side rendering with data\n- Implement user authentication and authorization using JSON web tokens\n- Set up and use React 360 to develop user interfaces with VR capabilities\n- Make your MERN stack applications reliable and scalable with industry best practices\n\nIf you feel this book is for you, get your [copy](https://www.amazon.com/dp/1839215410) today!\n\n---\n"
  },
  {
    "path": "client/App.js",
    "content": "import React from 'react'\nimport MainRouter from './MainRouter'\nimport {BrowserRouter} from 'react-router-dom'\nimport { ThemeProvider } from '@material-ui/styles'\nimport theme from './theme'\nimport { hot } from 'react-hot-loader'\n\nconst App = () => {\n  React.useEffect(() => {\n    const jssStyles = document.querySelector('#jss-server-side')\n    if (jssStyles) {\n      jssStyles.parentNode.removeChild(jssStyles)\n    }\n  }, [])\n  return (\n  <BrowserRouter>\n      <ThemeProvider theme={theme}>\n        <MainRouter/>\n      </ThemeProvider>\n  </BrowserRouter>\n)}\n\nexport default hot(module)(App)\n"
  },
  {
    "path": "client/MainRouter.js",
    "content": "import React, {Component} from 'react'\nimport {Route, Switch} from 'react-router-dom'\nimport Home from './core/Home'\nimport Users from './user/Users'\nimport Signup from './user/Signup'\nimport Signin from './auth/Signin'\nimport EditProfile from './user/EditProfile'\nimport Profile from './user/Profile'\nimport PrivateRoute from './auth/PrivateRoute'\nimport Menu from './core/Menu'\nimport NewMedia from './media/NewMedia'\nimport PlayMedia from './media/PlayMedia'\nimport EditMedia from './media/EditMedia'\n\nconst MainRouter = ({data}) => {\n  return (<div>\n      <Menu/>\n      <Switch>\n        <Route exact path=\"/\" component={Home}/>\n        <Route path=\"/users\" component={Users}/>\n        <Route path=\"/signup\" component={Signup}/>\n        <Route path=\"/signin\" component={Signin}/>\n        <PrivateRoute path=\"/user/edit/:userId\" component={EditProfile}/>\n        <Route path=\"/user/:userId\" component={Profile}/>\n\n        <PrivateRoute path=\"/media/new\" component={NewMedia}/>\n        <PrivateRoute path=\"/media/edit/:mediaId\" component={EditMedia}/>\n        <Route path=\"/media/:mediaId\" render={(props) => (\n            <PlayMedia {...props} data={data} />\n        )} />\n      </Switch>\n    </div>)\n}\n\nexport default MainRouter\n"
  },
  {
    "path": "client/auth/PrivateRoute.js",
    "content": "import React, { Component } from 'react'\nimport { Route, Redirect } from 'react-router-dom'\nimport auth from './auth-helper'\n\nconst PrivateRoute = ({ component: Component, ...rest }) => (\n  <Route {...rest} render={props => (\n    auth.isAuthenticated() ? (\n      <Component {...props}/>\n    ) : (\n      <Redirect to={{\n        pathname: '/signin',\n        state: { from: props.location }\n      }}/>\n    )\n  )}/>\n)\n\nexport default PrivateRoute\n"
  },
  {
    "path": "client/auth/Signin.js",
    "content": "import React, {useState} from 'react'\nimport Card from '@material-ui/core/Card'\nimport CardActions from '@material-ui/core/CardActions'\nimport CardContent from '@material-ui/core/CardContent'\nimport Button from '@material-ui/core/Button'\nimport TextField from '@material-ui/core/TextField'\nimport Typography from '@material-ui/core/Typography'\nimport Icon from '@material-ui/core/Icon'\nimport { makeStyles } from '@material-ui/core/styles'\nimport auth from './../auth/auth-helper'\nimport {Redirect} from 'react-router-dom'\nimport {signin} from './api-auth.js'\n\nconst useStyles = makeStyles(theme => ({\n  card: {\n    maxWidth: 600,\n    margin: 'auto',\n    textAlign: 'center',\n    marginTop: theme.spacing(5),\n    paddingBottom: theme.spacing(2)\n  },\n  error: {\n    verticalAlign: 'middle'\n  },\n  title: {\n    marginTop: theme.spacing(2),\n    color: theme.palette.openTitle\n  },\n  textField: {\n    marginLeft: theme.spacing(1),\n    marginRight: theme.spacing(1),\n    width: 300\n  },\n  submit: {\n    margin: 'auto',\n    marginBottom: theme.spacing(2)\n  }\n}))\n\nexport default function Signin(props) {\n  const classes = useStyles()\n  const [values, setValues] = useState({\n      email: '',\n      password: '',\n      error: '',\n      redirectToReferrer: false\n  })\n\n  const clickSubmit = () => {\n    const user = {\n      email: values.email || undefined,\n      password: values.password || undefined\n    }\n\n    signin(user).then((data) => {\n      if (data.error) {\n        setValues({ ...values, error: data.error})\n      } else {\n        auth.authenticate(data, () => {\n          setValues({ ...values, error: '',redirectToReferrer: true})\n        })\n      }\n    })\n  }\n\n  const handleChange = name => event => {\n    setValues({ ...values, [name]: event.target.value })\n  }\n\n  const {from} = props.location.state || {\n      from: {\n        pathname: '/'\n      }\n  }\n  const {redirectToReferrer} = values\n  if (redirectToReferrer) {\n      return (<Redirect to={from}/>)\n  }\n\n  return (\n      <Card className={classes.card}>\n        <CardContent>\n          <Typography variant=\"h6\" className={classes.title}>\n            Sign In\n          </Typography>\n          <TextField id=\"email\" type=\"email\" label=\"Email\" className={classes.textField} value={values.email} onChange={handleChange('email')} margin=\"normal\"/><br/>\n          <TextField id=\"password\" type=\"password\" label=\"Password\" className={classes.textField} value={values.password} onChange={handleChange('password')} margin=\"normal\"/>\n          <br/> {\n            values.error && (<Typography component=\"p\" color=\"error\">\n              <Icon color=\"error\" className={classes.error}>error</Icon>\n              {values.error}\n            </Typography>)\n          }\n        </CardContent>\n        <CardActions>\n          <Button color=\"primary\" variant=\"contained\" onClick={clickSubmit} className={classes.submit}>Submit</Button>\n        </CardActions>\n      </Card>\n    )\n}\n"
  },
  {
    "path": "client/auth/api-auth.js",
    "content": "const signin = async (user) => {\n  try {\n    let response = await fetch('/auth/signin/', {\n      method: 'POST',\n      headers: {\n        'Accept': 'application/json',\n        'Content-Type': 'application/json'\n      },\n      credentials: 'include',\n      body: JSON.stringify(user)\n    })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst signout = async () => {\n  try {\n    let response = await fetch('/auth/signout/', { method: 'GET' })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nexport {\n  signin,\n  signout\n}\n"
  },
  {
    "path": "client/auth/auth-helper.js",
    "content": "import { signout } from './api-auth.js'\n\nconst auth = {\n  isAuthenticated() {\n    if (typeof window == \"undefined\")\n      return false\n\n    if (sessionStorage.getItem('jwt'))\n      return JSON.parse(sessionStorage.getItem('jwt'))\n    else\n      return false\n  },\n  authenticate(jwt, cb) {\n    if (typeof window !== \"undefined\")\n      sessionStorage.setItem('jwt', JSON.stringify(jwt))\n    cb()\n  },\n  clearJWT(cb) {\n    if (typeof window !== \"undefined\")\n      sessionStorage.removeItem('jwt')\n    cb()\n    //optional\n    signout().then((data) => {\n      document.cookie = \"t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;\"\n    })\n  }\n}\n\nexport default auth"
  },
  {
    "path": "client/core/Home.js",
    "content": "import React, {useState, useEffect} from 'react'\nimport { makeStyles } from '@material-ui/core/styles'\nimport Card from '@material-ui/core/Card'\nimport Typography from '@material-ui/core/Typography'\nimport MediaList from '../media/MediaList'\nimport {listPopular} from '../media/api-media.js'\n\nconst useStyles = makeStyles(theme => ({\n  card: {\n    margin: `${theme.spacing(5)}px 30px`\n  },\n  title: {\n    padding:`${theme.spacing(3)}px ${theme.spacing(2.5)}px 0px`,\n    color: theme.palette.text.secondary,\n    fontSize: '1em'\n  },\n  media: {\n    minHeight: 330\n  }\n}))\n\nexport default function Home(){\n  const classes = useStyles()\n  const [media, setMedia] = useState([])\n\n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n    listPopular(signal).then((data) => {\n      if (data.error) {\n        console.log(data.error)\n      } else {\n        setMedia(data)\n      }\n    })\n    return function cleanup(){\n      abortController.abort()\n    }\n  }, [])\n  return (\n      <Card className={classes.card}>\n        <Typography variant=\"h2\" className={classes.title}>\n          Popular Videos\n        </Typography>\n          <MediaList media={media}/>\n      </Card>\n  )\n}\n"
  },
  {
    "path": "client/core/Menu.js",
    "content": "import React from 'react'\nimport AppBar from '@material-ui/core/AppBar'\nimport Toolbar from '@material-ui/core/Toolbar'\nimport Typography from '@material-ui/core/Typography'\nimport IconButton from '@material-ui/core/IconButton'\nimport HomeIcon from '@material-ui/icons/Home'\nimport AddBoxIcon from '@material-ui/icons/AddBox'\nimport Button from '@material-ui/core/Button'\nimport auth from './../auth/auth-helper'\nimport {Link, withRouter} from 'react-router-dom'\n\nconst isActive = (history, path) => {\n  if (history.location.pathname == path)\n    return {color: '#f99085'}\n  else\n    return {color: '#efdcd5'}\n}\nconst Menu = withRouter(({history}) => (\n  <AppBar position=\"static\">\n    <Toolbar>\n      <Typography type=\"title\" color=\"inherit\">\n        MERN Mediastream\n      </Typography>\n      <div>\n        <Link to=\"/\">\n          <IconButton aria-label=\"Home\" style={isActive(history, \"/\")}>\n            <HomeIcon/>\n          </IconButton>\n        </Link>\n      </div>\n      <div style={{'position':'absolute', 'right': '10px'}}><span style={{'float': 'right'}}>\n      {\n        !auth.isAuthenticated() && (<span>\n          <Link to=\"/signup\">\n            <Button style={isActive(history, \"/signup\")}>Sign up\n            </Button>\n          </Link>\n          <Link to=\"/signin\">\n            <Button style={isActive(history, \"/signin\")}>Sign In\n            </Button>\n          </Link>\n        </span>)\n      }\n      {\n        auth.isAuthenticated() && (<span>\n          <Link to=\"/media/new\">\n            <Button style={isActive(history, \"/media/new\")}>\n              <AddBoxIcon style={{marginRight: '8px'}}/> Add Media\n            </Button>\n          </Link>\n          <Link to={\"/user/\" + auth.isAuthenticated().user._id}>\n            <Button style={isActive(history, \"/user/\" + auth.isAuthenticated().user._id)}>My Profile</Button>\n          </Link>\n          <Button color=\"inherit\" onClick={() => {\n              auth.signout(() => history.push('/'))\n            }}>Sign out</Button>\n        </span>)\n      }\n      </span></div>\n    </Toolbar>\n  </AppBar>\n))\n\nexport default Menu\n"
  },
  {
    "path": "client/main.js",
    "content": "import React from 'react'\nimport { hydrate } from 'react-dom'\nimport App from './App'\n\nhydrate(<App/>, document.getElementById('root'))\n"
  },
  {
    "path": "client/media/DeleteMedia.js",
    "content": "import React, {useState} from 'react'\nimport PropTypes from 'prop-types'\nimport IconButton from '@material-ui/core/IconButton'\nimport Button from '@material-ui/core/Button'\nimport DeleteIcon from '@material-ui/icons/Delete'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport DialogContentText from '@material-ui/core/DialogContentText'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport auth from './../auth/auth-helper'\nimport {remove} from './api-media.js'\nimport {Redirect} from 'react-router-dom'\n\nexport default function DeleteMedia(props) {\n  const [open, setOpen] = useState(false)\n  const [redirect, setRedirect] = useState(false)\n  \n  const jwt = auth.isAuthenticated()\n  const clickButton = () => {\n    setOpen(true)\n  }\n  const deleteMedia = () => {\n    const jwt = auth.isAuthenticated()\n    remove({\n      mediaId: props.mediaId\n    }, {t: jwt.token}).then((data) => {\n      if (data.error) {\n        console.log(data.error)\n      } else {\n        setRedirect(true)\n      }\n    })\n  }\n  const handleRequestClose = () => {\n    setOpen(false)\n  }\n  if (redirect) {\n    return <Redirect to='/'/>\n  }\n  return (<span>\n    <IconButton aria-label=\"Delete\" onClick={clickButton} color=\"secondary\">\n      <DeleteIcon/>\n    </IconButton>\n\n    <Dialog open={open} onClose={handleRequestClose}>\n      <DialogTitle>{\"Delete \"+props.mediaTitle}</DialogTitle>\n      <DialogContent>\n        <DialogContentText>\n          Confirm to delete {props.mediaTitle} from your account.\n        </DialogContentText>\n      </DialogContent>\n      <DialogActions>\n        <Button onClick={handleRequestClose} color=\"primary\">\n          Cancel\n        </Button>\n        <Button onClick={deleteMedia} variant=\"contained\" color=\"secondary\" autoFocus=\"autoFocus\">\n          Confirm\n        </Button>\n      </DialogActions>\n    </Dialog>\n  </span>)\n}\n\nDeleteMedia.propTypes = {\n  mediaId: PropTypes.string.isRequired,\n  mediaTitle: PropTypes.string.isRequired\n}"
  },
  {
    "path": "client/media/EditMedia.js",
    "content": "import React, {useState, useEffect} from 'react'\nimport Card from '@material-ui/core/Card'\nimport CardActions from '@material-ui/core/CardActions'\nimport CardContent from '@material-ui/core/CardContent'\nimport Button from '@material-ui/core/Button'\nimport TextField from '@material-ui/core/TextField'\nimport Typography from '@material-ui/core/Typography'\nimport Icon from '@material-ui/core/Icon'\nimport { makeStyles } from '@material-ui/core/styles'\nimport auth from './../auth/auth-helper'\nimport {read, update} from './api-media.js'\nimport {Redirect} from 'react-router-dom'\n\nconst useStyles = makeStyles(theme => ({\n  card: {\n    maxWidth: 500,\n    margin: 'auto',\n    textAlign: 'center',\n    marginTop: theme.spacing(5),\n    paddingBottom: theme.spacing(2)\n  },\n  title: {\n    margin: theme.spacing(2),\n    color: theme.palette.protectedTitle,\n    fontSize: '1em'\n  },\n  error: {\n    verticalAlign: 'middle'\n  },\n  textField: {\n    marginLeft: theme.spacing(1),\n    marginRight: theme.spacing(1),\n    width: 300\n  },\n  submit: {\n    margin: 'auto',\n    marginBottom: theme.spacing(2)\n  },\n  input: {\n  display: 'none'\n},\nfilename:{\n  marginLeft:'10px'\n}\n}))\n\nexport default function EditProfile({ match }) {\n  const classes = useStyles()\n  const [media, setMedia] = useState({title: '', description:'', genre:''})\n  const [redirect, setRedirect] = useState(false)\n  const [error, setError] = useState('')\n  const jwt = auth.isAuthenticated()\n\n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n\n    read({mediaId: match.params.mediaId}).then((data) => {\n      if (data.error) {\n        setError(data.error)\n      } else {\n        setMedia(data)\n      }\n    })\n    return function cleanup(){\n      abortController.abort()\n    }\n  }, [match.params.mediaId])\n\n  const clickSubmit = () => {\n    const jwt = auth.isAuthenticated()\n    update({\n      mediaId: media._id\n    }, {\n      t: jwt.token\n    }, media).then((data) => {\n      if (data.error) {\n        setError(data.error)\n      } else {\n        setRedirect(true)\n      }\n    })\n  }\n\n  const handleChange = name => event => {\n    let updatedMedia = {...media}\n    updatedMedia[name] = event.target.value\n    setMedia(updatedMedia)\n  }\n    if (redirect) {\n      return (<Redirect to={'/media/' + media._id}/>)\n    }\n    return (\n      <Card className={classes.card}>\n        <CardContent>\n          <Typography type=\"headline\" component=\"h1\" className={classes.title}>\n            Edit Video Details\n          </Typography>\n          <TextField id=\"title\" label=\"Title\" className={classes.textField} value={media.title} onChange={handleChange('title')} margin=\"normal\"/><br/>\n          <TextField\n            id=\"multiline-flexible\"\n            label=\"Description\"\n            multiline\n            rows=\"2\"\n            value={media.description}\n            onChange={handleChange('description')}\n            className={classes.textField}\n            margin=\"normal\"\n          /><br/>\n          <TextField id=\"genre\" label=\"Genre\" className={classes.textField} value={media.genre} onChange={handleChange('genre')} margin=\"normal\"/><br/>\n          <br/> {\n                  error &&\n                  (<Typography component=\"p\" color=\"error\">\n                    <Icon color=\"error\" className={classes.error}>error</Icon>\n                    {error}\n                  </Typography>)\n                }\n        </CardContent>\n        <CardActions>\n          <Button color=\"primary\" variant=\"contained\" onClick={clickSubmit} className={classes.submit}>Submit</Button>\n        </CardActions>\n      </Card>\n    )\n  }\n"
  },
  {
    "path": "client/media/Media.js",
    "content": "import React from 'react'\nimport PropTypes from 'prop-types'\nimport { makeStyles } from '@material-ui/core/styles'\nimport Card from '@material-ui/core/Card'\nimport CardHeader from '@material-ui/core/CardHeader'\nimport List from '@material-ui/core/List'\nimport ListItem from '@material-ui/core/ListItem'\nimport ListItemAvatar from '@material-ui/core/ListItemAvatar'\nimport ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport IconButton from '@material-ui/core/IconButton'\nimport Edit from '@material-ui/icons/Edit'\nimport Avatar from '@material-ui/core/Avatar'\nimport auth from './../auth/auth-helper'\nimport {Link} from 'react-router-dom'\nimport Divider from '@material-ui/core/Divider'\nimport DeleteMedia from './DeleteMedia'\nimport MediaPlayer from './MediaPlayer'\n\nconst useStyles = makeStyles(theme => ({\n  card: {\n    padding:'20px'\n  },\n  header: {\n    padding:'0px 16px 16px 12px'\n  },\n  action: {\n    margin: '24px 20px 0px 0px',\n    display: 'inline-block',\n    fontSize: '1.15em',\n    color: theme.palette.secondary.dark\n  },\n  avatar: {\n      color: theme.palette.primary.contrastText,\n      backgroundColor: theme.palette.primary.light\n  }\n}))\n\nexport default function Media(props) {\n  const classes = useStyles()\n  const mediaUrl = props.media._id\n        ? `/api/media/video/${props.media._id}`\n        : null\n  const nextUrl = props.nextUrl\n  return (\n    <Card className={classes.card}>\n      <CardHeader className={classes.header}\n            title={props.media.title}\n            action={\n              <span className={classes.action}>{props.media.views + ' views'}</span>\n            }\n            subheader={props.media.genre}\n      />\n      <MediaPlayer srcUrl={mediaUrl} nextUrl={nextUrl} handleAutoplay={props.handleAutoplay}/>\n      <List dense>\n        <ListItem>\n          <ListItemAvatar>\n            <Avatar className={classes.avatar}>\n              {props.media.postedBy.name && props.media.postedBy.name[0]}\n            </Avatar>\n          </ListItemAvatar>\n          <ListItemText primary={props.media.postedBy.name}\n                        secondary={\"Published on \" + (new Date(props.media.created)).toDateString()}/>\n          { auth.isAuthenticated().user\n              && auth.isAuthenticated().user._id == props.media.postedBy._id\n              && (<ListItemSecondaryAction>\n                    <Link to={\"/media/edit/\" + props.media._id}>\n                      <IconButton aria-label=\"Edit\" color=\"secondary\">\n                        <Edit/>\n                      </IconButton>\n                    </Link>\n                    <DeleteMedia mediaId={props.media._id} mediaTitle={props.media.title}/>\n                  </ListItemSecondaryAction>)\n          }\n        </ListItem>\n        <Divider/>\n        <ListItem>\n          <ListItemText primary={props.media.description}/>\n        </ListItem>\n      </List>\n    </Card>)\n}\n\n\nMedia.propTypes = {\n  media: PropTypes.object,\n  nextUrl: PropTypes.string,\n  handleAutoplay: PropTypes.func.isRequired\n}\n\n"
  },
  {
    "path": "client/media/MediaList.js",
    "content": "import React from 'react'\nimport PropTypes from 'prop-types'\nimport { makeStyles } from '@material-ui/core/styles'\nimport GridList from '@material-ui/core/GridList'\nimport GridListTileBar from '@material-ui/core/GridListTileBar'\nimport GridListTile from '@material-ui/core/GridListTile'\nimport {Link} from 'react-router-dom'\nimport ReactPlayer from 'react-player'\n\nconst useStyles = makeStyles(theme => ({\n  root: {\n    display: 'flex',\n    flexWrap: 'wrap',\n    justifyContent: 'space-around',\n    overflow: 'hidden',\n    background: theme.palette.background.paper,\n    textAlign: 'left',\n    padding: '8px 16px'\n  },\n  gridList: {\n    width: '100%',\n    minHeight: 180,\n    padding: '0px 0 10px'\n  },\n  title: {\n    padding:`${theme.spacing(3)}px ${theme.spacing(2.5)}px ${theme.spacing(2)}px`,\n    color: theme.palette.openTitle,\n    width: '100%'\n  },\n  tile: {\n    textAlign: 'center',\n    maxHeight: '100%'\n  },\n  tileBar: {\n    backgroundColor: 'rgba(0, 0, 0, 0.72)',\n    textAlign: 'left',\n    height: '55px'\n  },\n  tileTitle: {\n    fontSize:'1.1em',\n    marginBottom:'5px',\n    color:'rgb(193, 173, 144)',\n    display:\"block\"\n  },\n  tileGenre: {\n    float: 'right',\n    color:'rgb(193, 182, 164)',\n    marginRight: '8px'\n  }\n}))\n\nexport default function MediaList(props) { \n  const classes = useStyles()\n  return (\n    <div className={classes.root}>\n      <GridList className={classes.gridList} cols={3}>\n        {props.media.map((tile, i) => (\n          <GridListTile key={i} className={classes.tile}>\n            <Link to={\"/media/\"+tile._id}>\n              <ReactPlayer url={'/api/media/video/'+tile._id} width='100%' height='inherit' style={{maxHeight: '100%'}}/>\n            </Link>\n            <GridListTileBar className={classes.tileBar}\n              title={<Link to={\"/media/\"+tile._id} className={classes.tileTitle}> {tile.title} </Link>}\n              subtitle={<span>\n                          <span>{tile.views} views</span>\n                          <span className={classes.tileGenre}>\n                            <em>{tile.genre}</em>\n                          </span>\n                        </span>}\n            />\n          </GridListTile>\n        ))}\n      </GridList>\n    </div>)\n}\n\nMediaList.propTypes = {\n  media: PropTypes.array.isRequired\n}\n\n"
  },
  {
    "path": "client/media/MediaPlayer.js",
    "content": "import React, {useState, useEffect, useRef} from 'react'\nimport { findDOMNode } from 'react-dom'\nimport screenfull from 'screenfull'\nimport IconButton from '@material-ui/core/IconButton'\nimport Icon from '@material-ui/core/Icon'\nimport PropTypes from 'prop-types'\nimport {makeStyles} from '@material-ui/core/styles'\nimport { Link } from 'react-router-dom'\nimport ReactPlayer from 'react-player'\nimport LinearProgress from '@material-ui/core/LinearProgress'\n\nconst useStyles = makeStyles(theme => ({\n  flex:{\n    display:'flex'\n  },\n  primaryDashed: {\n    background: 'none',\n    backgroundColor: theme.palette.secondary.main\n  },\n  primaryColor: {\n    backgroundColor: '#6969694f'\n  },\n  dashed: {\n    animation: 'none'\n  },\n  controls:{\n    position: 'relative',\n    backgroundColor: '#ababab52'\n  },\n  rangeRoot: {\n    position: 'absolute',\n    width: '100%',\n    top: '-7px',\n    zIndex: '3456',\n    '-webkit-appearance': 'none',\n    backgroundColor: 'rgba(0,0,0,0)'\n  },\n  videoError: {\n    width: '100%',\n    textAlign: 'center',\n    color: theme.palette.primary.light\n  }\n}))\n\nexport default function MediaPlayer(props) {\n  const classes = useStyles()\n  const [playing, setPlaying] = useState(false)\n  const [volume, setVolume] = useState(0.8)      \n  const [muted, setMuted] = useState(false)     \n  const [duration, setDuration] = useState(0)  \n  const [seeking, setSeeking] = useState(false)    \n  const [playbackRate, setPlaybackRate] = useState(1.0)     \n  const [loop, setLoop] = useState(false)      \n  const [fullscreen, setFullscreen] = useState(false)\n  const [videoError, setVideoError] = useState(false) \n  let playerRef = useRef(null)\n  const [values, setValues] = useState({\n    played: 0, loaded: 0, ended: false\n  })\n  \n  useEffect(() => {\n    if (screenfull.enabled) {\n      screenfull.on('change', () => {\n        let fullscreen = screenfull.isFullscreen ? true : false\n        setFullscreen(fullscreen)\n      })\n    }\n  }, [])\n  useEffect(() => {\n    setVideoError(false)\n  }, [props.srcUrl])\n  const changeVolume = e => {\n    setVolume(parseFloat(e.target.value))\n  }\n  const toggleMuted = () => {\n    setMuted(!muted)\n  }\n  const playPause = () => {\n    setPlaying(!playing)\n  }\n  const onLoop = () => {\n    setLoop(!loop)\n  }\n  const onProgress = progress => {\n    // We only want to update time slider if we are not currently seeking\n    if (!seeking) {\n      setValues({...values, played:progress.played, loaded: progress.loaded})\n    }\n  }\n  const onClickFullscreen = () => {\n   screenfull.request(findDOMNode(playerRef))\n  }\n  const onEnded = () => {\n    if(loop){\n      setPlaying(true)\n    } else{\n      props.handleAutoplay(()=>{\n        setValues({...values, ended:true}) \n        setPlaying(false)\n      })\n    }\n  }\n  const onDuration = (duration) => {\n    setDuration(duration)\n  }\n  const onSeekMouseDown = e => {\n    setSeeking(true)\n  }\n  const onSeekChange = e => {\n    setValues({...values, played:parseFloat(e.target.value), ended: parseFloat(e.target.value) >= 1})\n  }\n  const onSeekMouseUp = e => {\n    setSeeking(false)\n    playerRef.seekTo(parseFloat(e.target.value))\n  }\n  const ref = player => {\n      playerRef = player\n  }\n  const format = (seconds) => {\n    const date = new Date(seconds * 1000)\n    const hh = date.getUTCHours()\n    let mm = date.getUTCMinutes()\n    const ss = ('0' + date.getUTCSeconds()).slice(-2)\n    if (hh) {\n      mm = ('0' + date.getUTCMinutes()).slice(-2)\n      return `${hh}:${mm}:${ss}`\n    }\n    return `${mm}:${ss}`\n  }\n  const showVideoError = e => {\n    console.log(e)\n    setVideoError(true)\n  }\n\n  return (<div>\n    {videoError && <p className={classes.videoError}>Video Error. Try again later.</p>}\n      <div className={classes.flex}>\n        \n        <ReactPlayer\n          ref={ref}\n            width={fullscreen ? '100%':'inherit'}\n            height={fullscreen ? '100%':'inherit'}\n            style={fullscreen ? {position:'relative'} : {maxHeight: '500px'}}\n            config={{ attributes: { style: { height: '100%', width: '100%'} } }}\n            url={props.srcUrl}\n            playing={playing}\n            loop={loop}\n            playbackRate={playbackRate}\n            volume={volume}\n            muted={muted}\n            onEnded={onEnded}\n            onError={showVideoError}\n            onProgress={onProgress}\n            onDuration={onDuration}/>\n          <br/>\n      </div>\n      <div className={classes.controls}>\n        <LinearProgress color=\"primary\" variant=\"buffer\" value={values.played*100} valueBuffer={values.loaded*100} style={{width: '100%'}} classes={{\n              colorPrimary: classes.primaryColor,\n              dashedColorPrimary : classes.primaryDashed,\n              dashed: classes.dashed\n        }}/>\n        <input type=\"range\" min={0} max={1}\n                value={values.played} step='any'\n                onMouseDown={onSeekMouseDown}\n                onChange={onSeekChange}\n                onMouseUp={onSeekMouseUp}\n                className={classes.rangeRoot}/>\n\n        <IconButton color=\"primary\" onClick={playPause}>\n          <Icon>{playing ? 'pause': (values.ended ? 'replay' : 'play_arrow')}</Icon>\n        </IconButton>\n        <IconButton disabled={!props.nextUrl} color=\"primary\">\n          <Link to={props.nextUrl} style={{color: 'inherit'}}>\n            <Icon>skip_next</Icon>\n          </Link>\n        </IconButton>\n        <IconButton color=\"primary\" onClick={toggleMuted}>\n          <Icon>{volume > 0 && !muted && 'volume_up' || muted && 'volume_off' || volume==0 && 'volume_mute'}</Icon>\n        </IconButton>\n        <input type=\"range\" min={0} max={1} step='any' value={muted? 0 : volume} onChange={changeVolume} style={{verticalAlign: 'middle'}}/>\n        <IconButton color={loop? 'primary' : 'default'} onClick={onLoop}>\n          <Icon>loop</Icon>\n        </IconButton>\n        <IconButton color=\"primary\" onClick={onClickFullscreen}>\n          <Icon>fullscreen</Icon>\n        </IconButton>\n        <span style={{float: 'right', padding: '10px', color: '#b83423'}}>\n          <time dateTime={`P${Math.round(duration * values.played)}S`}>\n            {format(duration * values.played)}\n          </time> / <time dateTime={`P${Math.round(duration)}S`}>\n                        {format(duration)}\n                    </time>\n        </span>\n      </div>\n    </div>\n  )\n}\n\nMediaPlayer.propTypes = {\n  srcUrl: PropTypes.string,\n  nextUrl: PropTypes.string,\n  handleAutoplay: PropTypes.func.isRequired\n}\n"
  },
  {
    "path": "client/media/NewMedia.js",
    "content": "import React, {useState} from 'react'\nimport auth from './../auth/auth-helper'\nimport Card from '@material-ui/core/Card'\nimport CardActions from '@material-ui/core/CardActions'\nimport CardContent from '@material-ui/core/CardContent'\nimport Button from '@material-ui/core/Button'\nimport TextField from '@material-ui/core/TextField'\nimport Typography from '@material-ui/core/Typography'\nimport FileUpload from '@material-ui/icons/AddToQueue'\nimport Icon from '@material-ui/core/Icon'\nimport {makeStyles} from '@material-ui/core/styles'\nimport {create} from './api-media.js'\nimport {Redirect} from 'react-router-dom'\n\nconst useStyles = makeStyles(theme => ({\n  card: {\n    maxWidth: 500,\n    margin: 'auto',\n    textAlign: 'center',\n    marginTop: theme.spacing(5),\n    paddingBottom: theme.spacing(2)\n  },\n  title: {\n    margin: theme.spacing(2),\n    color: theme.palette.protectedTitle,\n    fontSize: '1em'\n  },\n  error: {\n    verticalAlign: 'middle'\n  },\n  textField: {\n    marginLeft: theme.spacing(1),\n    marginRight: theme.spacing(1),\n    width: 300\n  },\n  submit: {\n    margin: 'auto',\n    marginBottom: theme.spacing(2)\n  },\n  input: {\n    display: 'none'\n  },\n  filename:{\n    marginLeft:'10px'\n  }\n}))\n\nexport default function NewMedia(){\n  const classes = useStyles()\n  const [values, setValues] = useState({\n      title: '',\n      video: '',\n      description: '',\n      genre: '',\n      redirect: false,\n      error: '',\n      mediaId: ''\n  })\n  const jwt = auth.isAuthenticated()\n\n  const clickSubmit = () => {\n    let mediaData = new FormData()\n    values.title && mediaData.append('title', values.title)\n    values.video && mediaData.append('video', values.video)\n    values.description && mediaData.append('description', values.description)\n    values.genre && mediaData.append('genre', values.genre)\n    create({\n      userId: jwt.user._id\n    }, {\n      t: jwt.token\n    }, mediaData).then((data) => {\n      if (data.error) {\n        setValues({...values, error: data.error})\n      } else {\n        setValues({...values, error: '', mediaId: data._id, redirect: true})\n      }\n    })\n  }\n\n  const handleChange = name => event => {\n    const value = name === 'video'\n      ? event.target.files[0]\n      : event.target.value\n    setValues({...values, [name]: value })\n  }\n\n    if (values.redirect) {\n      return (<Redirect to={'/media/' + values.mediaId}/>)\n    }\n    return (\n      <Card className={classes.card}>\n        <CardContent>\n          <Typography type=\"headline\" component=\"h1\" className={classes.title}>\n            New Video\n          </Typography>\n          <input accept=\"video/*\" onChange={handleChange('video')} className={classes.input} id=\"icon-button-file\" type=\"file\" />\n          <label htmlFor=\"icon-button-file\">\n            <Button color=\"secondary\" variant=\"contained\" component=\"span\">\n              Upload\n              <FileUpload/>\n            </Button>\n          </label> <span className={classes.filename}>{values.video ? values.video.name : ''}</span><br/>\n          <TextField id=\"title\" label=\"Title\" className={classes.textField} value={values.title} onChange={handleChange('title')} margin=\"normal\"/><br/>\n          <TextField\n            id=\"multiline-flexible\"\n            label=\"Description\"\n            multiline\n            rows=\"2\"\n            value={values.description}\n            onChange={handleChange('description')}\n            className={classes.textField}\n            margin=\"normal\"\n          /><br/>\n          <TextField id=\"genre\" label=\"Genre\" className={classes.textField} value={values.genre} onChange={handleChange('genre')} margin=\"normal\"/><br/>\n          <br/> {\n                  values.error && (<Typography component=\"p\" color=\"error\">\n                      <Icon color=\"error\" className={classes.error}>error</Icon>\n                      {values.error}\n                    </Typography>)\n                }\n        </CardContent>\n        <CardActions>\n          <Button color=\"primary\" variant=\"contained\" onClick={clickSubmit} className={classes.submit}>Submit</Button>\n        </CardActions>\n      </Card>\n    )\n  }\n\n\n\n"
  },
  {
    "path": "client/media/PlayMedia.js",
    "content": "import React, {useState, useEffect} from 'react'\nimport PropTypes from 'prop-types'\nimport {makeStyles} from '@material-ui/core/styles'\nimport Grid from '@material-ui/core/Grid'\nimport {read, listRelated} from './api-media.js'\nimport Media from './Media'\nimport RelatedMedia from './RelatedMedia'\nimport FormControlLabel from '@material-ui/core/FormControlLabel'\nimport Switch from '@material-ui/core/Switch'\n\nconst useStyles = makeStyles(theme => ({\n  root: {\n    flexGrow: 1,\n    margin: 30,\n  },\n  toggle: {\n    float: 'right',\n    marginRight: '30px',\n    marginTop:' 10px'\n  }\n}))\n\nexport default function PlayMedia(props) {\n  const classes = useStyles()\n  let [media, setMedia] = useState({postedBy: {}})\n  let [relatedMedia, setRelatedMedia] = useState([])\n  const [autoPlay, setAutoPlay] = useState(false)\n\n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n\n    read({mediaId: props.match.params.mediaId}, signal).then((data) => {\n      if (data && data.error) {\n        console.log(data.error)\n      } else {\n        setMedia(data)\n      }\n    })\n    return function cleanup(){\n      abortController.abort()\n    }\n  }, [props.match.params.mediaId])\n\n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n\n    listRelated({\n      mediaId: props.match.params.mediaId}, signal).then((data) => {\n      if (data.error) {\n        console.log(data.error)\n      } else {\n        setRelatedMedia(data)\n      }\n    })\n    return function cleanup(){\n      abortController.abort()\n    }\n  }, [props.match.params.mediaId])\n\n  const handleChange = (event) => {\n   setAutoPlay(event.target.checked)\n  }\n  const handleAutoplay = (updateMediaControls) => {\n    let playList = relatedMedia\n    let playMedia = playList[0]\n    if(!autoPlay || playList.length == 0 )\n      return updateMediaControls()\n   \n    if(playList.length > 1){\n      playList.shift()\n      setMedia(playMedia)\n      setRelatedMedia(playList)\n    }else{\n      listRelated({\n          mediaId: playMedia._id}).then((data) => {\n            if (data.error) {\n             console.log(data.error)\n            } else {\n              setMedia(playMedia)\n              setRelatedMedia(data)\n            }\n         })\n    }\n  }\n  //render SSR data\n    if (props.data && props.data[0] != null) {\n          media = props.data[0]\n          relatedMedia = []\n    }\n\n    const nextUrl = relatedMedia.length > 0\n          ? `/media/${relatedMedia[0]._id}` : ''\n    return (\n      <div className={classes.root}>\n        <Grid container spacing={8}>\n          <Grid item xs={8} sm={8}>\n            <Media media={media} nextUrl={nextUrl} handleAutoplay={handleAutoplay}/>\n          </Grid>\n          {relatedMedia.length > 0\n            && (<Grid item xs={4} sm={4}>\n                    <FormControlLabel className = {classes.toggle}\n                        control={\n                          <Switch\n                            checked={autoPlay}\n                            onChange={handleChange}\n                            color=\"primary\"\n                          />\n                        }\n                        label={autoPlay ? 'Autoplay ON':'Autoplay OFF'}\n                    />\n                  <RelatedMedia media={relatedMedia}/>\n                </Grid>)\n           }\n        </Grid>\n      </div>)\n  }\n\n"
  },
  {
    "path": "client/media/RelatedMedia.js",
    "content": "import React from 'react'\nimport PropTypes from 'prop-types'\nimport {makeStyles} from '@material-ui/core/styles'\nimport Paper from '@material-ui/core/Paper'\nimport Typography from '@material-ui/core/Typography'\nimport {Link} from 'react-router-dom'\nimport Divider from '@material-ui/core/Divider'\nimport Card from '@material-ui/core/Card'\nimport CardContent from '@material-ui/core/CardContent'\nimport ReactPlayer from 'react-player'\n\nconst useStyles = makeStyles(theme => ({\n  root: theme.mixins.gutters({\n    paddingBottom: 24,\n    backgroundColor: '#80808024'\n  }),\n  title: {\n    margin: `${theme.spacing(3)}px ${theme.spacing(1)}px ${theme.spacing(2)}px`,\n    color: theme.palette.openTitle,\n    fontSize: '1em'\n  },\n  card: {\n    width: '100%',\n    display: 'inline-flex'\n  },\n  details: {\n    display: 'inline-block',\n    width: \"100%\"\n  },\n  content: {\n    flex: '1 0 auto',\n    padding: '16px 8px 0px'\n  },\n  controls: {\n    marginTop: '8px'\n  },\n  date: {\n    color: 'rgba(0, 0, 0, 0.4)'\n  },\n  mediaTitle: {\n    whiteSpace: 'nowrap',\n    overflow: 'hidden',\n    textOverflow: 'ellipsis',\n    width: '130px',\n    fontSize: '1em',\n    marginBottom: '5px'\n  },\n  subheading: {\n    color: 'rgba(88, 114, 128, 0.67)'\n  },\n  views: {\n    display: 'inline',\n    lineHeight: '3',\n    paddingLeft: '8px',\n    color: theme.palette.text.secondary\n  }\n}))\nexport default function RelatedMedia(props) {\n  const classes = useStyles()\n    return (\n      <Paper className={classes.root} elevation={4} style={{padding: '16px'}}>\n          <Typography type=\"title\" className={classes.title}>\n            Up Next\n          </Typography>\n          {props.media.map((item, i) => {\n              return <span key={i}><Card className={classes.card} >\n                <div style={{marginRight: \"5px\", backgroundColor: \"black\"}}>\n              <Link to={\"/media/\"+item._id}><ReactPlayer url={'/api/media/video/'+item._id} width='160px' height='140px'/></Link>\n              </div>\n                      <div className={classes.details}>\n                        <CardContent className={classes.content}>\n                          <Link to={'/media/'+item._id}><Typography type=\"title\" component=\"h3\" className={classes.mediaTitle} color=\"primary\">{item.title}</Typography></Link>\n                          <Typography type=\"subheading\" className={classes.subheading}>\n                            {item.genre}\n                          </Typography>\n\n                          <Typography component=\"p\" className={classes.date}>\n                            {(new Date(item.created)).toDateString()}\n                          </Typography>\n\n                        </CardContent>\n                        <div className={classes.controls}>\n                        <Typography type=\"subheading\" component=\"h3\" className={classes.views} color=\"primary\"> {item.views} views</Typography>\n                        </div>\n                      </div>\n\n                    </Card>\n                    <Divider/>\n                    </span>\n            })\n          }\n      </Paper>\n    )\n  }\n\nRelatedMedia.propTypes = {\n  media: PropTypes.array.isRequired\n}"
  },
  {
    "path": "client/media/api-media.js",
    "content": "import config from '../../config/config'\nconst create = async (params, credentials, media) => {\n  try {\n    let response = await fetch('/api/media/new/'+ params.userId, {\n    method: 'POST',\n    headers: {\n      'Accept': 'application/json',\n      'Authorization': 'Bearer ' + credentials.t\n    },\n    body: media\n  })    \n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst listPopular = async (signal) => {\n  try {\n    let response = await fetch('/api/media/popular', {\n    method: 'GET',\n    signal: signal,\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json'\n    }\n  })\n      return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst listByUser = async (params) => {\n  try {\n    let response = await fetch('/api/media/by/'+ params.userId, {\n    method: 'GET',\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json'\n    }\n  })\n      return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst read = async (params, signal) => {\n  try {\n    let response = await fetch(config.serverUrl +'/api/media/' + params.mediaId, {\n    method: 'GET',\n    signal: signal\n  })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst update = async (params, credentials, media) => {\n  try {\n    let response = await fetch('/api/media/' + params.mediaId, {\n    method: 'PUT',\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json',\n      'Authorization': 'Bearer ' + credentials.t\n    },\n    body: JSON.stringify(media)\n  })    \n    return await response.json()\n    } catch(err) {\n      console.log(err)\n    }\n}\n\nconst remove = async (params, credentials) => {\n  try {\n    let response = await fetch('/api/media/' + params.mediaId, {\n    method: 'DELETE',\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json',\n      'Authorization': 'Bearer ' + credentials.t\n    }\n  })    \n  return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst listRelated = async (params, signal) => {\n  try {\n    let response = await fetch('/api/media/related/'+ params.mediaId, {\n    method: 'GET',\n    signal: signal,\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json'\n    }\n  })\n      return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nexport {\n  create,\n  listPopular,\n  listByUser,\n  read,\n  update,\n  remove,\n  listRelated\n}\n"
  },
  {
    "path": "client/routeConfig.js",
    "content": "import PlayMedia from './media/PlayMedia'\nimport { read } from './media/api-media.js'\n\nconst routes = [\n  {\n    path: '/media/:mediaId',\n    component: PlayMedia,\n    loadData: (params) => read(params)\n  }\n\n]\nexport default routes\n"
  },
  {
    "path": "client/theme.js",
    "content": "import { createMuiTheme } from '@material-ui/core/styles'\nimport { red, brown } from '@material-ui/core/colors'\n\nconst theme = createMuiTheme({\n    typography: {\n      useNextVariants: true,\n    },\n    palette: {\n      primary: {\n        light: '#f05545',\n        main: '#b71c1c',\n        dark: '#7f0000',\n        contrastText: '#fff',\n      },\n      secondary: {\n        light: '#fbfffc',\n        main: '#c8e6c9',\n        dark: '#97b498',\n        contrastText: '#37474f',\n      },\n        openTitle: red['500'],\n        protectedTitle: brown['300'],\n        type: 'light'\n      },\n    })\n\n  export default theme"
  },
  {
    "path": "client/user/DeleteUser.js",
    "content": "import React, {useState} from 'react'\nimport PropTypes from 'prop-types'\nimport IconButton from '@material-ui/core/IconButton'\nimport Button from '@material-ui/core/Button'\nimport DeleteIcon from '@material-ui/icons/Delete'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport DialogContentText from '@material-ui/core/DialogContentText'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport auth from './../auth/auth-helper'\nimport {remove} from './api-user.js'\nimport {Redirect} from 'react-router-dom'\n\nexport default function DeleteUser(props) {\n  const [open, setOpen] = useState(false)\n  const [redirect, setRedirect] = useState(false)\n\n  const jwt = auth.isAuthenticated()\n  const clickButton = () => {\n    setOpen(true)\n  }\n  const deleteAccount = () => { \n    remove({\n      userId: props.userId\n    }, {t: jwt.token}).then((data) => {\n      if (data && data.error) {\n        console.log(data.error)\n      } else {\n        auth.clearJWT(() => console.log('deleted'))\n        setRedirect(true)\n      }\n    })\n  }\n  const handleRequestClose = () => {\n    setOpen(false)\n  }\n\n  if (redirect) {\n    return <Redirect to='/'/>\n  }\n    return (<span>\n      <IconButton aria-label=\"Delete\" onClick={clickButton} color=\"secondary\">\n        <DeleteIcon/>\n      </IconButton>\n\n      <Dialog open={open} onClose={handleRequestClose}>\n        <DialogTitle>{\"Delete Account\"}</DialogTitle>\n        <DialogContent>\n          <DialogContentText>\n            Confirm to delete your account.\n          </DialogContentText>\n        </DialogContent>\n        <DialogActions>\n          <Button onClick={handleRequestClose} color=\"primary\">\n            Cancel\n          </Button>\n          <Button onClick={deleteAccount} color=\"secondary\" autoFocus=\"autoFocus\">\n            Confirm\n          </Button>\n        </DialogActions>\n      </Dialog>\n    </span>)\n\n}\nDeleteUser.propTypes = {\n  userId: PropTypes.string.isRequired\n}\n\n"
  },
  {
    "path": "client/user/EditProfile.js",
    "content": "import React, {useState, useEffect} from 'react'\nimport Card from '@material-ui/core/Card'\nimport CardActions from '@material-ui/core/CardActions'\nimport CardContent from '@material-ui/core/CardContent'\nimport Button from '@material-ui/core/Button'\nimport TextField from '@material-ui/core/TextField'\nimport Typography from '@material-ui/core/Typography'\nimport Icon from '@material-ui/core/Icon'\nimport { makeStyles } from '@material-ui/core/styles'\nimport auth from './../auth/auth-helper'\nimport {read, update} from './api-user.js'\nimport {Redirect} from 'react-router-dom'\n\nconst useStyles = makeStyles(theme => ({\n  card: {\n    maxWidth: 600,\n    margin: 'auto',\n    textAlign: 'center',\n    marginTop: theme.spacing(5),\n    paddingBottom: theme.spacing(2)\n  },\n  title: {\n    margin: theme.spacing(2),\n    color: theme.palette.protectedTitle\n  },\n  error: {\n    verticalAlign: 'middle'\n  },\n  textField: {\n    marginLeft: theme.spacing(1),\n    marginRight: theme.spacing(1),\n    width: 300\n  },\n  submit: {\n    margin: 'auto',\n    marginBottom: theme.spacing(2)\n  }\n}))\n\nexport default function EditProfile({ match }) {\n  const classes = useStyles()\n  const [values, setValues] = useState({\n    name: '',\n    password: '',\n    email: '',\n    open: false,\n    error: '',\n    redirectToProfile: false\n  })\n  const jwt = auth.isAuthenticated()\n\n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n\n    read({\n      userId: match.params.userId\n    }, {t: jwt.token}, signal).then((data) => {\n      if (data && data.error) {\n        setValues({...values, error: data.error})\n      } else {\n        setValues({...values, name: data.name, email: data.email})\n      }\n    })\n    return function cleanup(){\n      abortController.abort()\n    }\n\n  }, [match.params.userId])\n\n  const clickSubmit = () => {\n    const user = {\n      name: values.name || undefined,\n      email: values.email || undefined,\n      password: values.password || undefined\n    }\n    update({\n      userId: match.params.userId\n    }, {\n      t: jwt.token\n    }, user).then((data) => {\n      if (data && data.error) {\n        setValues({...values, error: data.error})\n      } else {\n        setValues({...values, userId: data._id, redirectToProfile: true})\n      }\n    })\n  }\n  const handleChange = name => event => {\n    setValues({...values, [name]: event.target.value})\n  }\n\n    if (values.redirectToProfile) {\n      return (<Redirect to={'/user/' + values.userId}/>)\n    }\n    return (\n      <Card className={classes.card}>\n        <CardContent>\n          <Typography variant=\"h6\" className={classes.title}>\n            Edit Profile\n          </Typography>\n          <TextField id=\"name\" label=\"Name\" className={classes.textField} value={values.name} onChange={handleChange('name')} margin=\"normal\"/><br/>\n          <TextField id=\"email\" type=\"email\" label=\"Email\" className={classes.textField} value={values.email} onChange={handleChange('email')} margin=\"normal\"/><br/>\n          <TextField id=\"password\" type=\"password\" label=\"Password\" className={classes.textField} value={values.password} onChange={handleChange('password')} margin=\"normal\"/>\n          <br/> {\n            values.error && (<Typography component=\"p\" color=\"error\">\n              <Icon color=\"error\" className={classes.error}>error</Icon>\n              {values.error}\n            </Typography>)\n          }\n        </CardContent>\n        <CardActions>\n          <Button color=\"primary\" variant=\"contained\" onClick={clickSubmit} className={classes.submit}>Submit</Button>\n        </CardActions>\n      </Card>\n    )\n}"
  },
  {
    "path": "client/user/Profile.js",
    "content": "import React, { useState, useEffect } from 'react'\nimport { makeStyles } from '@material-ui/core/styles'\nimport Paper from '@material-ui/core/Paper'\nimport List from '@material-ui/core/List'\nimport ListItem from '@material-ui/core/ListItem'\nimport ListItemAvatar from '@material-ui/core/ListItemAvatar'\nimport ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport Avatar from '@material-ui/core/Avatar'\nimport IconButton from '@material-ui/core/IconButton'\nimport Typography from '@material-ui/core/Typography'\nimport Edit from '@material-ui/icons/Edit'\nimport Person from '@material-ui/icons/Person'\nimport Divider from '@material-ui/core/Divider'\nimport DeleteUser from './DeleteUser'\nimport auth from './../auth/auth-helper'\nimport {read} from './api-user.js'\nimport {Redirect, Link} from 'react-router-dom'\nimport {listByUser} from '../media/api-media.js'\nimport MediaList from '../media/MediaList'\n\nconst useStyles = makeStyles(theme => ({\n  root: theme.mixins.gutters({\n    maxWidth: 600,\n    margin: 'auto',\n    padding: theme.spacing(3),\n    marginTop: theme.spacing(5)\n  }),\n  title: {\n    marginTop: theme.spacing(3),\n    color: theme.palette.protectedTitle\n  },\n  avatar: {\n    color: theme.palette.primary.contrastText,\n    backgroundColor: theme.palette.primary.light\n  }\n}))\n\nexport default function Profile({ match }) {\n  const classes = useStyles()\n  const [user, setUser] = useState({})\n  const [redirectToSignin, setRedirectToSignin] = useState(false)\n  const jwt = auth.isAuthenticated()\n  const [media, setMedia] = useState([])\n\n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n\n    read({\n      userId: match.params.userId\n    }, {t: jwt.token}, signal).then((data) => {\n      if (data && data.error) {\n        setRedirectToSignin(true)\n      } else {\n        setUser(data)\n      }\n    })\n\n    return function cleanup(){\n      abortController.abort()\n    }\n\n  }, [match.params.userId])\n  \n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n\n    listByUser({\n      userId: match.params.userId\n    }, {t: jwt.token}, signal).then((data) => {\n      if (data && data.error) {\n        setRedirectToSignin(true)\n      } else {\n        setMedia(data)\n      }\n    })\n\n    return function cleanup(){\n      abortController.abort()\n    }\n\n  }, [match.params.userId])\n\n    if (redirectToSignin) {\n      return <Redirect to='/signin'/>\n    }\n    return (\n      <Paper className={classes.root} elevation={4}>\n        <Typography variant=\"h6\" className={classes.title}>\n          Profile\n        </Typography>\n        <List dense>\n          <ListItem>\n            <ListItemAvatar>\n              <Avatar className={classes.avatar}>\n                {user.name && user.name[0]}\n              </Avatar>\n            </ListItemAvatar>\n            <ListItemText primary={user.name} secondary={user.email}/> {\n             auth.isAuthenticated().user && auth.isAuthenticated().user._id == user._id &&\n              (<ListItemSecondaryAction>\n                <Link to={\"/user/edit/\" + user._id}>\n                  <IconButton aria-label=\"Edit\" color=\"primary\">\n                    <Edit/>\n                  </IconButton>\n                </Link>\n                <DeleteUser userId={user._id}/>\n              </ListItemSecondaryAction>)\n            }\n          </ListItem>\n          <Divider/>\n          <ListItem>\n            <ListItemText primary={\"Joined: \" + (\n              new Date(user.created)).toDateString()}/>\n          </ListItem>\n          <MediaList media={media}/>\n        </List>\n      </Paper>\n    )\n  }"
  },
  {
    "path": "client/user/Signup.js",
    "content": "import React, {useState} from 'react'\nimport Card from '@material-ui/core/Card'\nimport CardActions from '@material-ui/core/CardActions'\nimport CardContent from '@material-ui/core/CardContent'\nimport Button from '@material-ui/core/Button'\nimport TextField from '@material-ui/core/TextField'\nimport Typography from '@material-ui/core/Typography'\nimport Icon from '@material-ui/core/Icon'\nimport { makeStyles } from '@material-ui/core/styles'\nimport {create} from './api-user.js'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport DialogContentText from '@material-ui/core/DialogContentText'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport {Link} from 'react-router-dom'\n\nconst useStyles = makeStyles(theme => ({\n  card: {\n    maxWidth: 600,\n    margin: 'auto',\n    textAlign: 'center',\n    marginTop: theme.spacing(5),\n    paddingBottom: theme.spacing(2)\n  },\n  error: {\n    verticalAlign: 'middle'\n  },\n  title: {\n    marginTop: theme.spacing(2),\n    color: theme.palette.openTitle\n  },\n  textField: {\n    marginLeft: theme.spacing(1),\n    marginRight: theme.spacing(1),\n    width: 300\n  },\n  submit: {\n    margin: 'auto',\n    marginBottom: theme.spacing(2)\n  }\n}))\n\nexport default function Signup() {\n  const classes = useStyles()\n  const [values, setValues] = useState({\n    name: '',\n    password: '',\n    email: '',\n    open: false,\n    error: ''\n  })\n\n  const handleChange = name => event => {\n    setValues({ ...values, [name]: event.target.value })\n  }\n\n  const clickSubmit = () => {\n    const user = {\n      name: values.name || undefined,\n      email: values.email || undefined,\n      password: values.password || undefined\n    }\n    create(user).then((data) => {\n      if (data.error) {\n        setValues({ ...values, error: data.error})\n      } else {\n        setValues({ ...values, error: '', open: true})\n      }\n    })\n  }\n\n    return (<div>\n      <Card className={classes.card}>\n        <CardContent>\n          <Typography variant=\"h6\" className={classes.title}>\n            Sign Up\n          </Typography>\n          <TextField id=\"name\" label=\"Name\" className={classes.textField} value={values.name} onChange={handleChange('name')} margin=\"normal\"/><br/>\n          <TextField id=\"email\" type=\"email\" label=\"Email\" className={classes.textField} value={values.email} onChange={handleChange('email')} margin=\"normal\"/><br/>\n          <TextField id=\"password\" type=\"password\" label=\"Password\" className={classes.textField} value={values.password} onChange={handleChange('password')} margin=\"normal\"/>\n          <br/> {\n            values.error && (<Typography component=\"p\" color=\"error\">\n              <Icon color=\"error\" className={classes.error}>error</Icon>\n              {values.error}</Typography>)\n          }\n        </CardContent>\n        <CardActions>\n          <Button color=\"primary\" variant=\"contained\" onClick={clickSubmit} className={classes.submit}>Submit</Button>\n        </CardActions>\n      </Card>\n      <Dialog open={values.open} disableBackdropClick={true}>\n        <DialogTitle>New Account</DialogTitle>\n        <DialogContent>\n          <DialogContentText>\n            New account successfully created.\n          </DialogContentText>\n        </DialogContent>\n        <DialogActions>\n          <Link to=\"/signin\">\n            <Button color=\"primary\" autoFocus=\"autoFocus\" variant=\"contained\">\n              Sign In\n            </Button>\n          </Link>\n        </DialogActions>\n      </Dialog>\n    </div>\n    )\n}"
  },
  {
    "path": "client/user/Users.js",
    "content": "import React, {useState, useEffect} from 'react'\nimport { makeStyles } from '@material-ui/core/styles'\nimport Paper from '@material-ui/core/Paper'\nimport List from '@material-ui/core/List'\nimport ListItem from '@material-ui/core/ListItem'\nimport ListItemAvatar from '@material-ui/core/ListItemAvatar'\nimport ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport Avatar from '@material-ui/core/Avatar'\nimport IconButton from '@material-ui/core/IconButton'\nimport Typography from '@material-ui/core/Typography'\nimport ArrowForward from '@material-ui/icons/ArrowForward'\nimport Person from '@material-ui/icons/Person'\nimport {Link} from 'react-router-dom'\nimport {list} from './api-user.js'\n\nconst useStyles = makeStyles(theme => ({\n  root: theme.mixins.gutters({\n    padding: theme.spacing(1),\n    margin: theme.spacing(5)\n  }),\n  title: {\n    margin: `${theme.spacing(4)}px 0 ${theme.spacing(2)}px`,\n    color: theme.palette.openTitle\n  }\n}))\n\nexport default function Users() { \n  const classes = useStyles()\n  const [users, setUsers] = useState([])\n\n  useEffect(() => {\n    const abortController = new AbortController()\n    const signal = abortController.signal\n\n    list(signal).then((data) => {\n      if (data && data.error) {\n        console.log(data.error)\n      } else {\n        setUsers(data)\n      }\n    })\n\n    return function cleanup(){\n      abortController.abort()\n    }\n  }, [])\n\n\n    return (\n      <Paper className={classes.root} elevation={4}>\n        <Typography variant=\"h6\" className={classes.title}>\n          All Users\n        </Typography>\n        <List dense>\n         {users.map((item, i) => {\n          return <Link to={\"/user/\" + item._id} key={i}>\n                    <ListItem button>\n                      <ListItemAvatar>\n                        <Avatar>\n                          <Person/>\n                        </Avatar>\n                      </ListItemAvatar>\n                      <ListItemText primary={item.name}/>\n                      <ListItemSecondaryAction>\n                      <IconButton>\n                          <ArrowForward/>\n                      </IconButton>\n                      </ListItemSecondaryAction>\n                    </ListItem>\n                 </Link>\n               })\n             }\n        </List>\n      </Paper>\n    )\n}\n"
  },
  {
    "path": "client/user/api-user.js",
    "content": "const create = async (user) => {\n  try {\n      let response = await fetch('/api/users/', {\n        method: 'POST',\n        headers: {\n          'Accept': 'application/json',\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(user)\n      })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst list = async (signal) => {\n  try {\n    let response = await fetch('/api/users/', {\n      method: 'GET',\n      signal: signal,\n    })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst read = async (params, credentials, signal) => {\n  try {\n    let response = await fetch('/api/users/' + params.userId, {\n      method: 'GET',\n      signal: signal,\n      headers: {\n        'Accept': 'application/json',\n        'Content-Type': 'application/json',\n        'Authorization': 'Bearer ' + credentials.t\n      }\n    })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst update = async (params, credentials, user) => {\n  try {\n    let response = await fetch('/api/users/' + params.userId, {\n      method: 'PUT',\n      headers: {\n        'Accept': 'application/json',\n        'Content-Type': 'application/json',\n        'Authorization': 'Bearer ' + credentials.t\n      },\n      body: JSON.stringify(user)\n    })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nconst remove = async (params, credentials) => {\n  try {\n    let response = await fetch('/api/users/' + params.userId, {\n      method: 'DELETE',\n      headers: {\n        'Accept': 'application/json',\n        'Content-Type': 'application/json',\n        'Authorization': 'Bearer ' + credentials.t\n      }\n    })\n    return await response.json()\n  } catch(err) {\n    console.log(err)\n  }\n}\n\nexport {\n  create,\n  list,\n  read,\n  update,\n  remove\n}"
  },
  {
    "path": "config/config.js",
    "content": "const config = {\n  env: process.env.NODE_ENV || 'development',\n  port: process.env.PORT || 3000,\n  jwtSecret: process.env.JWT_SECRET || \"YOUR_secret_key\",\n  mongoUri: process.env.MONGODB_URI ||\n    process.env.MONGO_HOST ||\n    'mongodb://' + (process.env.IP || 'localhost') + ':' +\n    (process.env.MONGO_PORT || '27017') +\n    '/mernproject',\n  serverUrl: process.env.serverUrl || 'http://localhost:3000'\n}\n\nexport default config\n"
  },
  {
    "path": "nodemon.json",
    "content": "{\n    \"verbose\": false,\n    \"watch\": [\n      \"./server\"\n    ],\n    \"exec\": \"webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mern-mediastream\",\n  \"version\": \"2.0.0\",\n  \"description\": \"A MERN stack based media streaming application\",\n  \"author\": \"Shama Hoque\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"react\",\n    \"express\",\n    \"mongodb\",\n    \"node\",\n    \"mern\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/shamahoque/mern-mediastream.git\"\n  },\n  \"homepage\": \"https://github.com/shamahoque/mern-mediastream\",\n  \"main\": \"./dist/server.generated.js\",\n  \"scripts\": {\n    \"development\": \"nodemon\",\n    \"build\": \"webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js\",\n    \"start\": \"NODE_ENV=production node ./dist/server.generated.js\"\n  },\n  \"engines\": {\n    \"node\": \"13.12.0\",\n    \"npm\": \"6.14.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"7.9.0\",\n    \"@babel/preset-env\": \"7.9.0\",\n    \"@babel/preset-react\": \"7.9.4\",\n    \"babel-loader\": \"8.1.0\",\n    \"file-loader\": \"6.0.0\",\n    \"nodemon\": \"2.0.2\",\n    \"webpack\": \"4.42.1\",\n    \"webpack-cli\": \"3.3.11\",\n    \"webpack-dev-middleware\": \"3.7.2\",\n    \"webpack-hot-middleware\": \"2.25.0\",\n    \"webpack-node-externals\": \"1.7.2\"\n  },\n  \"dependencies\": {\n    \"@hot-loader/react-dom\": \"16.13.0\",\n    \"@material-ui/core\": \"4.9.8\",\n    \"@material-ui/icons\": \"4.9.1\",\n    \"body-parser\": \"1.19.0\",\n    \"compression\": \"1.7.4\",\n    \"cookie-parser\": \"1.4.5\",\n    \"cors\": \"2.8.5\",\n    \"express\": \"4.17.1\",\n    \"express-jwt\": \"5.3.1\",\n    \"formidable\": \"1.2.2\",\n    \"helmet\": \"3.22.0\",\n    \"isomorphic-fetch\": \"2.2.1\",\n    \"jsonwebtoken\": \"8.5.1\",\n    \"lodash\": \"4.17.15\",\n    \"mongoose\": \"5.9.7\",\n    \"react\": \"16.13.1\",\n    \"react-dom\": \"16.13.1\",\n    \"react-hot-loader\": \"4.12.20\",\n    \"react-player\": \"1.15.3\",\n    \"react-router\": \"5.1.2\",\n    \"react-router-config\": \"5.1.1\",\n    \"react-router-dom\": \"5.1.2\",\n    \"screenfull\": \"5.0.2\"\n  }\n}\n"
  },
  {
    "path": "server/controllers/auth.controller.js",
    "content": "import User from '../models/user.model'\nimport jwt from 'jsonwebtoken'\nimport expressJwt from 'express-jwt'\nimport config from './../../config/config'\n\nconst signin = async (req, res) => {\n  try {\n    let user = await User.findOne({\n      \"email\": req.body.email\n    })\n    if (!user)\n      return res.status('401').json({\n        error: \"User not found\"\n      })\n\n    if (!user.authenticate(req.body.password)) {\n      return res.status('401').send({\n        error: \"Email and password don't match.\"\n      })\n    }\n\n    const token = jwt.sign({\n      _id: user._id\n    }, config.jwtSecret)\n\n    res.cookie(\"t\", token, {\n      expire: new Date() + 9999\n    })\n\n    return res.json({\n      token,\n      user: {\n        _id: user._id,\n        name: user.name,\n        email: user.email\n      }\n    })\n\n  } catch (err) {\n\n    return res.status('401').json({\n      error: \"Could not sign in\"\n    })\n\n  }\n}\n\nconst signout = (req, res) => {\n  res.clearCookie(\"t\")\n  return res.status('200').json({\n    message: \"signed out\"\n  })\n}\n\nconst requireSignin = expressJwt({\n  secret: config.jwtSecret,\n  userProperty: 'auth'\n})\n\nconst hasAuthorization = (req, res, next) => {\n  const authorized = req.profile && req.auth && req.profile._id == req.auth._id\n  if (!(authorized)) {\n    return res.status('403').json({\n      error: \"User is not authorized\"\n    })\n  }\n  next()\n}\n\nexport default {\n  signin,\n  signout,\n  requireSignin,\n  hasAuthorization\n}\n"
  },
  {
    "path": "server/controllers/media.controller.js",
    "content": "import Media from '../models/media.model'\nimport extend from 'lodash/extend'\nimport errorHandler from './../helpers/dbErrorHandler'\nimport formidable from 'formidable'\nimport fs from 'fs'\n\n//media streaming\nimport mongoose from 'mongoose'\nlet gridfs = null\nmongoose.connection.on('connected', () => {\n  gridfs = new mongoose.mongo.GridFSBucket(mongoose.connection.db)\n})\n\nconst create = (req, res) => {\n  let form = new formidable.IncomingForm()\n  form.keepExtensions = true\n  form.parse(req, async (err, fields, files) => {\n      if (err) {\n        return res.status(400).json({\n          error: \"Video could not be uploaded\"\n        })\n      }\n      let media = new Media(fields)\n      media.postedBy= req.profile\n      if(files.video){\n        let writestream = gridfs.openUploadStream(media._id, {\n          contentType: files.video.type || 'binary/octet-stream'})\n        fs.createReadStream(files.video.path).pipe(writestream)\n      }\n      try {\n        let result = await media.save()\n        res.status(200).json(result)\n      }\n      catch (err){\n          return res.status(400).json({\n            error: errorHandler.getErrorMessage(err)\n          })\n      }\n    })\n}\n\nconst mediaByID = async (req, res, next, id) => {\n  try{\n  let media = await Media.findById(id).populate('postedBy', '_id name').exec()\n    if (!media)\n      return res.status('400').json({\n        error: \"Media not found\"\n      })\n      req.media = media\n      let files = await gridfs.find({filename:media._id}).toArray()\n        if (!files[0]) {\n          return res.status(404).send({\n            error: 'No video found'\n          })\n        }     \n        req.file = files[0]\n        next()\n    }catch(err) {\n      return res.status(404).send({\n        error: 'Could not retrieve media file'\n      })\n    }\n}\n\nconst video = (req, res) => {\n  const range = req.headers[\"range\"]\n  if (range && typeof range === \"string\") {\n    const parts = range.replace(/bytes=/, \"\").split(\"-\")\n    const partialstart = parts[0]\n    const partialend = parts[1]\n\n    const start = parseInt(partialstart, 10)\n    const end = partialend ? parseInt(partialend, 10) : req.file.length - 1\n    const chunksize = (end - start) + 1\n\n    res.writeHead(206, {\n        'Accept-Ranges': 'bytes',\n        'Content-Length': chunksize,\n        'Content-Range': 'bytes ' + start + '-' + end + '/' + req.file.length,\n        'Content-Type': req.file.contentType\n    })\n\n    let downloadStream = gridfs.openDownloadStream(req.file._id, {start, end: end+1})\n    downloadStream.pipe(res)\n    downloadStream.on('error', () => {\n      res.sendStatus(404)\n    })\n    downloadStream.on('end', () => {\n      res.end()\n    })\n  } else {\n      res.header('Content-Length', req.file.length)\n      res.header('Content-Type', req.file.contentType)\n\n      let downloadStream = gridfs.openDownloadStream(req.file._id)\n      downloadStream.pipe(res)\n      downloadStream.on('error', () => {\n        res.sendStatus(404)\n      })\n      downloadStream.on('end', () => {\n        res.end()\n      })\n  }\n}\n\nconst listPopular = async (req, res) => {\n  try{\n    let media = await Media.find({}).limit(9)\n    .populate('postedBy', '_id name')\n    .sort('-views')\n    .exec()\n    res.json(media)\n  } catch(err){\n    return res.status(400).json({\n      error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\nconst listByUser = async (req, res) => {\n  try{\n    let media = await Media.find({postedBy: req.profile._id})\n      .populate('postedBy', '_id name')\n      .sort('-created')\n      .exec()\n    res.json(media)\n  } catch(err){\n      return res.status(400).json({\n        error: errorHandler.getErrorMessage(err)\n      })\n  }\n}\n\nconst read = (req, res) => {\n  return res.json(req.media)\n}\n\nconst incrementViews = async (req, res, next) => {\n  try {\n    await Media.findByIdAndUpdate(req.media._id, {$inc: {\"views\": 1}}, {new: true}).exec()\n    next()\n  } catch(err){\n      return res.status(400).json({\n          error: errorHandler.getErrorMessage(err)\n      })\n  }\n}\n\nconst update = async (req, res) => {\n  try {\n    let media = req.media\n    media = extend(media, req.body)\n    media.updated = Date.now()\n    await media.save()\n    res.json(media)\n  } catch(err){\n    return res.status(400).json({\n        error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\nconst isPoster = (req, res, next) => {\n  let isPoster = req.media && req.auth && req.media.postedBy._id == req.auth._id\n  if(!isPoster){\n    return res.status('403').json({\n      error: \"User is not authorized\"\n    })\n  }\n  next()\n}\n\nconst remove = async (req, res) => {\n  try {\n    let media = req.media\n    let deletedMedia = await media.remove()\n    gridfs.delete(req.file._id)\n    res.json(deletedMedia)\n  } catch(err) {\n    return res.status(400).json({\n      error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\nconst listRelated = async (req, res) => {\n  try {\n    let media = await Media.find({ \"_id\": { \"$ne\": req.media }, \"genre\": req.media.genre})\n      .limit(4)\n      .sort('-views')\n      .populate('postedBy', '_id name')\n      .exec()\n    res.json(media)\n  } catch (err) {\n    return res.status(400).json({\n      error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\nexport default {\n  create,\n  mediaByID,\n  video,\n  listPopular,\n  listByUser,\n  read,\n  incrementViews,\n  update,\n  isPoster,\n  remove,\n  listRelated\n}\n"
  },
  {
    "path": "server/controllers/user.controller.js",
    "content": "import User from '../models/user.model'\nimport extend from 'lodash/extend'\nimport errorHandler from './../helpers/dbErrorHandler'\n\nconst create = async (req, res) => {\n  const user = new User(req.body)\n  try {\n    await user.save()\n    return res.status(200).json({\n      message: \"Successfully signed up!\"\n    })\n  } catch (err) {\n    return res.status(400).json({\n      error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\n/**\n * Load user and append to req.\n */\nconst userByID = async (req, res, next, id) => {\n  try {\n    let user = await User.findById(id)\n    if (!user)\n      return res.status('400').json({\n        error: \"User not found\"\n      })\n    req.profile = user\n    next()\n  } catch (err) {\n    return res.status('400').json({\n      error: \"Could not retrieve user\"\n    })\n  }\n}\n\nconst read = (req, res) => {\n  req.profile.hashed_password = undefined\n  req.profile.salt = undefined\n  return res.json(req.profile)\n}\n\nconst list = async (req, res) => {\n  try {\n    let users = await User.find().select('name email updated created')\n    res.json(users)\n  } catch (err) {\n    return res.status(400).json({\n      error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\nconst update = async (req, res) => {\n  try {\n    let user = req.profile\n    user = extend(user, req.body)\n    user.updated = Date.now()\n    await user.save()\n    user.hashed_password = undefined\n    user.salt = undefined\n    res.json(user)\n  } catch (err) {\n    return res.status(400).json({\n      error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\nconst remove = async (req, res) => {\n  try {\n    let user = req.profile\n    let deletedUser = await user.remove()\n    deletedUser.hashed_password = undefined\n    deletedUser.salt = undefined\n    res.json(deletedUser)\n  } catch (err) {\n    return res.status(400).json({\n      error: errorHandler.getErrorMessage(err)\n    })\n  }\n}\n\nexport default {\n  create,\n  userByID,\n  read,\n  list,\n  remove,\n  update\n}"
  },
  {
    "path": "server/devBundle.js",
    "content": "import config from './../config/config'\nimport webpack from 'webpack'\nimport webpackMiddleware from 'webpack-dev-middleware'\nimport webpackHotMiddleware from 'webpack-hot-middleware'\nimport webpackConfig from './../webpack.config.client.js'\n\nconst compile = (app) => {\n  if(config.env === \"development\"){\n    const compiler = webpack(webpackConfig)\n    const middleware = webpackMiddleware(compiler, {\n      publicPath: webpackConfig.output.publicPath\n    })\n    app.use(middleware)\n    app.use(webpackHotMiddleware(compiler))\n  }\n}\n\nexport default {\n  compile\n}\n"
  },
  {
    "path": "server/express.js",
    "content": "import express from 'express'\nimport path from 'path'\nimport bodyParser from 'body-parser'\nimport cookieParser from 'cookie-parser'\nimport compress from 'compression'\nimport cors from 'cors'\nimport helmet from 'helmet'\nimport Template from './../template'\nimport userRoutes from './routes/user.routes'\nimport authRoutes from './routes/auth.routes'\nimport mediaRoutes from './routes/media.routes'\n\n// modules for server side rendering\nimport React from 'react'\nimport ReactDOMServer from 'react-dom/server'\nimport MainRouter from './../client/MainRouter'\nimport { StaticRouter } from 'react-router-dom'\n\nimport { ServerStyleSheets, ThemeProvider } from '@material-ui/styles'\nimport theme from './../client/theme'\n//end\n\n//For SSR with data\nimport { matchRoutes } from 'react-router-config'\nimport routes from './../client/routeConfig'\nimport 'isomorphic-fetch'\n//end\n\n//comment out before building for production\nimport devBundle from './devBundle'\n\nconst CURRENT_WORKING_DIR = process.cwd()\nconst app = express()\n\n//comment out before building for production\ndevBundle.compile(app)\n\n//For SSR with data\nconst loadBranchData = (location) => {\n  const branch = matchRoutes(routes, location)\n  const promises = branch.map(({ route, match }) => {\n    return route.loadData\n      ? route.loadData(branch[0].match.params)\n      : Promise.resolve(null)\n  })\n  return Promise.all(promises)\n}\n\n// parse body params and attache them to req.body\napp.use(bodyParser.json())\napp.use(bodyParser.urlencoded({ extended: true }))\napp.use(cookieParser())\napp.use(compress())\n// secure apps by setting various HTTP headers\napp.use(helmet())\n// enable CORS - Cross Origin Resource Sharing\napp.use(cors())\n\napp.use('/dist', express.static(path.join(CURRENT_WORKING_DIR, 'dist')))\n\n// mount routes\napp.use('/', userRoutes)\napp.use('/', authRoutes)\napp.use('/', mediaRoutes)\n\napp.get('*', (req, res) => {\n  const sheets = new ServerStyleSheets()\n  const context = {}\n\n   loadBranchData(req.url).then(data => {\n       const markup = ReactDOMServer.renderToString(\n        sheets.collect(\n         <StaticRouter location={req.url} context={context}>\n             <ThemeProvider theme={theme}>\n                  <MainRouter data={data}/>\n             </ThemeProvider>\n          </StaticRouter>\n        )\n      )\n       if (context.url) {\n        return res.redirect(303, context.url)\n       }\n       const css = sheets.toString()\n       res.status(200).send(Template({\n          markup: markup,\n          css: css\n       }))\n   }).catch(err => {\n      res.status(500).send({\"error\": \"Could not load React view with data\"})\n  })\n})\n\n// Catch unauthorised errors\napp.use((err, req, res, next) => {\n  if (err.name === 'UnauthorizedError') {\n    res.status(401).json({\"error\" : err.name + \": \" + err.message})\n  }else if (err) {\n    res.status(400).json({\"error\" : err.name + \": \" + err.message})\n    console.log(err)\n  }\n})\n\nexport default app\n"
  },
  {
    "path": "server/helpers/dbErrorHandler.js",
    "content": "'use strict'\n\n/**\n * Get unique error field name\n */\nconst getUniqueErrorMessage = (err) => {\n    let output\n    try {\n        let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1'))\n        output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists'\n    } catch (ex) {\n        output = 'Unique field already exists'\n    }\n\n    return output\n}\n\n/**\n * Get the error message from error object\n */\nconst getErrorMessage = (err) => {\n    let message = ''\n\n    if (err.code) {\n        switch (err.code) {\n            case 11000:\n            case 11001:\n                message = getUniqueErrorMessage(err)\n                break\n            default:\n                message = 'Something went wrong'\n        }\n    } else {\n        for (let errName in err.errors) {\n            if (err.errors[errName].message) message = err.errors[errName].message\n        }\n    }\n\n    return message\n}\n\nexport default {getErrorMessage}\n"
  },
  {
    "path": "server/models/media.model.js",
    "content": "import mongoose from 'mongoose'\nconst MediaSchema = new mongoose.Schema({\n  title: {\n    type: String,\n    required: 'title is required'\n  },\n  description: String,\n  genre: String,\n  views: {type: Number, default: 0},\n  postedBy: {type: mongoose.Schema.ObjectId, ref: 'User'},\n  created: {\n    type: Date,\n    default: Date.now\n  },\n  updated: {\n    type: Date\n  }\n})\n\nexport default mongoose.model('Media', MediaSchema)\n"
  },
  {
    "path": "server/models/user.model.js",
    "content": "import mongoose from 'mongoose'\nimport crypto from 'crypto'\nconst UserSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    trim: true,\n    required: 'Name is required'\n  },\n  email: {\n    type: String,\n    trim: true,\n    unique: 'Email already exists',\n    match: [/.+\\@.+\\..+/, 'Please fill a valid email address'],\n    required: 'Email is required'\n  },\n  hashed_password: {\n    type: String,\n    required: \"Password is required\"\n  },\n  salt: String,\n  updated: Date,\n  created: {\n    type: Date,\n    default: Date.now\n  }\n})\n\nUserSchema\n  .virtual('password')\n  .set(function(password) {\n    this._password = password\n    this.salt = this.makeSalt()\n    this.hashed_password = this.encryptPassword(password)\n  })\n  .get(function() {\n    return this._password\n  })\n\nUserSchema.path('hashed_password').validate(function(v) {\n  if (this._password && this._password.length < 6) {\n    this.invalidate('password', 'Password must be at least 6 characters.')\n  }\n  if (this.isNew && !this._password) {\n    this.invalidate('password', 'Password is required')\n  }\n}, null)\n\nUserSchema.methods = {\n  authenticate: function(plainText) {\n    return this.encryptPassword(plainText) === this.hashed_password\n  },\n  encryptPassword: function(password) {\n    if (!password) return ''\n    try {\n      return crypto\n        .createHmac('sha1', this.salt)\n        .update(password)\n        .digest('hex')\n    } catch (err) {\n      return ''\n    }\n  },\n  makeSalt: function() {\n    return Math.round((new Date().valueOf() * Math.random())) + ''\n  }\n}\n\nexport default mongoose.model('User', UserSchema)\n"
  },
  {
    "path": "server/routes/auth.routes.js",
    "content": "import express from 'express'\nimport authCtrl from '../controllers/auth.controller'\n\nconst router = express.Router()\n\nrouter.route('/auth/signin')\n  .post(authCtrl.signin)\nrouter.route('/auth/signout')\n  .get(authCtrl.signout)\n\nexport default router\n"
  },
  {
    "path": "server/routes/media.routes.js",
    "content": "import express from 'express'\nimport userCtrl from '../controllers/user.controller'\nimport authCtrl from '../controllers/auth.controller'\nimport mediaCtrl from '../controllers/media.controller'\n\nconst router = express.Router()\n\nrouter.route('/api/media/new/:userId')\n    .post(authCtrl.requireSignin, mediaCtrl.create)\n\nrouter.route('/api/media/video/:mediaId')\n    .get(mediaCtrl.video)\n\nrouter.route('/api/media/popular')\n    .get(mediaCtrl.listPopular)\n\nrouter.route('/api/media/related/:mediaId')\n    .get(mediaCtrl.listRelated)\n\nrouter.route('/api/media/by/:userId')\n    .get(mediaCtrl.listByUser)\n\nrouter.route('/api/media/:mediaId')\n    .get( mediaCtrl.incrementViews, mediaCtrl.read)\n    .put(authCtrl.requireSignin, mediaCtrl.isPoster, mediaCtrl.update)\n    .delete(authCtrl.requireSignin, mediaCtrl.isPoster, mediaCtrl.remove)\n\nrouter.param('userId', userCtrl.userByID)\nrouter.param('mediaId', mediaCtrl.mediaByID)\n\nexport default router\n"
  },
  {
    "path": "server/routes/user.routes.js",
    "content": "import express from 'express'\nimport userCtrl from '../controllers/user.controller'\nimport authCtrl from '../controllers/auth.controller'\n\nconst router = express.Router()\n\nrouter.route('/api/users')\n  .get(userCtrl.list)\n  .post(userCtrl.create)\n\nrouter.route('/api/users/:userId')\n  .get(authCtrl.requireSignin, userCtrl.read)\n  .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update)\n  .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove)\n\nrouter.param('userId', userCtrl.userByID)\n\nexport default router\n"
  },
  {
    "path": "server/server.js",
    "content": "import config from './../config/config'\nimport app from './express'\nimport mongoose from 'mongoose'\n\n// Connection URL\nmongoose.Promise = global.Promise\nmongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: false })\nmongoose.connection.on('error', () => {\n  throw new Error(`unable to connect to database: ${config.mongoUri}`)\n})\n\napp.listen(config.port, (err) => {\n  if (err) {\n    console.log(err)\n  }\n  console.info('Server started on port %s.', config.port)\n})\n"
  },
  {
    "path": "template.js",
    "content": "export default ({markup, css}) => {\n    return `<!doctype html>\n      <html lang=\"en\">\n        <head>\n          <meta charset=\"utf-8\">\n          <title>MERN Mediastream</title>\n          <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Roboto:100,300,400\">\n          <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/icon?family=Material+Icons\">\n          <style>\n              a{\n                text-decoration: none\n              }\n          </style>\n        </head>\n        <body style=\"margin:0\">\n          <div id=\"root\">${markup}</div>\n          <style id=\"jss-server-side\">${css}</style>\n          <script type=\"text/javascript\" src=\"/dist/bundle.js\"></script>\n        </body>\n      </html>`\n}\n"
  },
  {
    "path": "webpack.config.client.js",
    "content": "const path = require('path')\nconst webpack = require('webpack')\nconst CURRENT_WORKING_DIR = process.cwd()\n\nconst config = {\n    name: \"browser\",\n    mode: \"development\",\n    devtool: 'eval-source-map',\n    entry: [\n        'react-hot-loader/patch',\n        'webpack-hot-middleware/client?reload=true',\n        path.join(CURRENT_WORKING_DIR, 'client/main.js')\n    ],\n    output: {\n        path: path.join(CURRENT_WORKING_DIR , '/dist'),\n        filename: 'bundle.js',\n        publicPath: '/dist/'\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.jsx?$/,\n                exclude: /node_modules/,\n                use: [\n                    'babel-loader'\n                ]\n            },\n            {\n                test: /\\.(ttf|eot|svg|gif|jpg|png)(\\?[\\s\\S]+)?$/,\n                use: 'file-loader'\n            }\n        ]\n    },  plugins: [\n          new webpack.HotModuleReplacementPlugin(),\n          new webpack.NoEmitOnErrorsPlugin()\n      ]\n}\n\nmodule.exports = config\n"
  },
  {
    "path": "webpack.config.client.production.js",
    "content": "const path = require('path')\nconst CURRENT_WORKING_DIR = process.cwd()\n\nconst config = {\n    mode: \"production\",\n    entry: [\n        path.join(CURRENT_WORKING_DIR, 'client/main.js')\n    ],\n    output: {\n        path: path.join(CURRENT_WORKING_DIR , '/dist'),\n        filename: 'bundle.js',\n        publicPath: \"/dist/\"\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.jsx?$/,\n                exclude: /node_modules/,\n                use: [\n                    'babel-loader'\n                ]\n            },\n            {\n                test: /\\.(ttf|eot|svg|gif|jpg|png)(\\?[\\s\\S]+)?$/,\n                use: 'file-loader'\n            }\n        ]\n    }\n}\n\nmodule.exports = config\n"
  },
  {
    "path": "webpack.config.server.js",
    "content": "const path = require('path')\nconst nodeExternals = require('webpack-node-externals')\nconst CURRENT_WORKING_DIR = process.cwd()\n\nconst config = {\n    name: \"server\",\n    entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ],\n    target: \"node\",\n    output: {\n        path: path.join(CURRENT_WORKING_DIR , '/dist/'),\n        filename: \"server.generated.js\",\n        publicPath: '/dist/',\n        libraryTarget: \"commonjs2\"\n    },\n    externals: [nodeExternals()],\n    module: {\n        rules: [\n            {\n                test: /\\.js$/,\n                exclude: /node_modules/,\n                use: [ 'babel-loader' ]\n            },\n            {\n                test: /\\.(ttf|eot|svg|gif|jpg|png)(\\?[\\s\\S]+)?$/,\n                use: 'file-loader'\n            }\n        ]\n    }\n}\n\nmodule.exports = config\n"
  }
]