suhanw / auteur

Auteur is a full-stack blogging app inspired by Tumblr. Express.js backend, MongoDB, and React.js with Redux framework.
https://www.suhanwijaya.com/auteur
1 stars 0 forks source link

Auteur

Auteur

Auteur is a full-stack web application inspired by Tumblr. It utilizes Node.js on the backend, a MongoDB database, and React.js with a Redux architectural framework on the frontend.

Features and Implementation

Single Page

Auteur is a single page app that allows for quick navigation between its various components. As data is fetched from NodeJS, components are only updated when necessary.

Carousel

Integrated React stateful components, CSS keyframe animations, and DOM events to create a carousel that responds to arrow keys, strokes on the touchpad, and mouse clicks.

Carousel

Posts

To keep code DRY, leveraged Higher Order Components and component composition to render different types of posts and new/edit forms.

Post Type

Incorporated AWS SDK to upload images to AWS S3 and persist image data to MongoDB

AWS

Notes (Likes and Comments)

On the backend, designed a polymorphic and extensible MongoDB schema for different note types such as likes and comments.

Notes

Following Blogs

Integrated React and CSS3 keyframes to animate page elements and improve user experience.

Following

Tags and Search

Built a search feature with autocomplete that suggests hashtags ranked by popularity. Integrated React and CSS3 Flexbox to accomplish a ‘masonry’ layout for search results.

Search

Chat

Leveraged WebSockets to implement the chat feature.

Chat

Implemented online indicator.

Online Indicator

Notifications

Leveraged WebSockets to implement the notifications feature. Likes and comments will create real-time in-app notifications for the relevant user.

Notifications

Responsive Layout

Enabled selective rendering and responsive layout for mobile devices via CSS media query.

Responsive Responsive Masonry

Design Patterns

class GlobalContextProvider extends React.Component { constructor(props) { super(props); }

render() { const { children, currentUser } = this.props; return ( <GlobalContext.Provider value={{ currentUser, }} > {children} </GlobalContext.Provider> ); } }


```javascript
// deeply nested component
import { GlobalContext } from '../../global_ context_provider';

class PostShowItem extends React.Component {
  renderPostContent() {
    const { currentUser } = this.context;

    if (!currentUser.following.includes(blog._id)) {
      // suggest user to follow blog
    }
  }
}

PostShowItem.contextType = GlobalContext;
// posts.js - API endpoint to create new post
router.post('/posts',
  middleware.isLoggedIn,
  upload.array('newFiles'), 
  function (req, res) {
    // STEP 1 - find blog
    modelQuery.findOneBlog(req.params.id)
      .then((foundBlog) => {
        let postBody = lodash.merge({}, req.body);
        // ...
        // STEP 2 - add tags to post
        return modelQuery.addTagsToPost(postBody, foundBlog);
      })
      .then(({ post, blog }) => {
        let newPost = new Post(post);
        // STEP 3a - if a media post, upload files to AWS
        if (['photo', 'video', 'audio'].includes(newPost.type)) {
          // ...
          return mediaUtil.uploadFiles(req.files, newPost, blog);
        }
        // STEP 3b - if not a media post, pass on to the next 'then' block
        return { post: newPost, blog: blog };
      })
      .then(({ post, blog }) => { 
        // ...
        // STEP 4 - save the new post
        return post.save()
      })
      .then((post) => {
        // STEP 5 - include post's tags 
        return post.populate({ path: 'tags', select: 'label' })
          .execPopulate();
      })
      .then((post) => {
        // STEP 6 - send http reponse json
        return res.json(post);
      })
      // STEP 7 - handle error(s)
      .catch((err) => res.status(422).json([err.message]));
  });

Notes

To run locally: