{"id":13818926,"url":"https://github.com/tylerreichle/Whenevernote","last_synced_at":"2025-05-16T04:31:56.872Z","repository":{"id":86570260,"uuid":"91285944","full_name":"tylerreichle/Whenevernote","owner":"tylerreichle","description":"Full-stack web application inspired by Evernote built using React/Redux and Ruby on Rails with PostgreSQL","archived":false,"fork":false,"pushed_at":"2018-10-10T06:34:27.000Z","size":31722,"stargazers_count":12,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"develop","last_synced_at":"2024-11-19T18:44:58.620Z","etag":null,"topics":["full-stack","postgresql","rails","react","redux","ruby-on-rails","single-page-app"],"latest_commit_sha":null,"homepage":"https://whenevernote.herokuapp.com/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tylerreichle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-15T02:10:28.000Z","updated_at":"2024-02-29T19:50:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"e079fdf2-b83c-41fb-86da-8a22843db89d","html_url":"https://github.com/tylerreichle/Whenevernote","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerreichle%2FWhenevernote","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerreichle%2FWhenevernote/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerreichle%2FWhenevernote/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerreichle%2FWhenevernote/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tylerreichle","download_url":"https://codeload.github.com/tylerreichle/Whenevernote/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254469025,"owners_count":22076415,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["full-stack","postgresql","rails","react","redux","ruby-on-rails","single-page-app"],"created_at":"2024-08-04T08:00:35.860Z","updated_at":"2025-05-16T04:31:54.787Z","avatar_url":"https://github.com/tylerreichle.png","language":"Ruby","funding_links":[],"categories":["Happy Exploring 🤘"],"sub_categories":[],"readme":"# [Whenevernote](https://whenevernote.herokuapp.com)\n\n![travis status](https://travis-ci.org/tylerreichle/Whenevernote.svg?branch=master)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/tylerreichle/Whenevernote/blob/master/LICENSE.md)\n\n\nWhenevernote is a full-stack web application inspired by Evernote built using Ruby on Rails and React/Redux with PostgreSQL. Whenevernote allows users to take notes, create to-do lists, and quickly write down their thoughts. These notes can be tagged and stored in notebooks for quick lookup whenever needed.\n\n![Splash Page](./docs/screenshots/splash.gif)\nSplash Page with Session Form component and Webm video background\n\n## Features\n- User accounts with secure authentication\n- Create, edit, and delete an unlimited number of notes within your virtual notebook\n- Personalize notes by adding lists, checkboxes, highlighting and using code blocks\n- Organize notes by placing them into personal notebooks\n- Categorize notes by tagging them with multiple tags\n- View all notes in a notebook and all notes with a certain tag\n- Notes are auto-saved upon edits so users have less to worry about\n\n## Requirements\n - node `6.10.1`\n - rails `\u003e5.0.7`\n - postgreql\n - yarn\n - bundler\n\n## Running Locally\n  1. Clone repo\n  2. Navigate to project folder\n  3. Install project dependencies:\n\n```\nbundle install\nyarn install\n```\n\n  4. Create local database and seed\n\n```\n// create postgres database\nbundle exec rake db:create\n\n// seed database (optional)\nbundle exec rake db:seed\n```\n\n  5. Start local server and webpack\n\n```\n// In separate terminal windows\n\n// Start rails server\nrails server\n\n// Run webpack watch\nyarn start\n```\n\n  6. Open browser and navigate to `localhost:3000`\n\n### Issues getting application to run?\n\n`¯\\_(ツ)_/¯`\n\n\n## Project Design\n\nWhenevernote was designed and built over the course of two weeks. A [proposal](./docs/README.md) was drafted to help form a timeline and guide through the app's development. The proposal included [wireframes](./docs/wireframes) for all views, a front-end [sample state](./docs/sample_state.md), and [database schema](./docs/schema.md). Project follows airbnb's JavaScript and React style guides to ensure clean and maintainable code.\n\n## Technology\n\n### Backend\n\nWhenevernote was built using Ruby on Rails and is hosted on Heroku. The different rails controllers send back the proper JSON responses depending on which API route was hit. PostgreSQL is used to manage the database locally and remotely.\n\n#### Dependencies\n\n- BCrypt for password salting and hashing to ensure user data remains secure and that plain text passwords are never stored.\n- The splash page video and images in the app are hosted through Amazon Web Services and Cloudinary to ensure a seamless and scalable user experience.\n- CSRF prevention by including a verification token in all forms protecting users from malicious attacks\n\n\nPassword hashing and user authentication inside of the User model. Password input from the user validated to be at least 8 characters requiring a strong password from the user. User's password is then salted and hashed with 128-bit encryption before being commited to database and plain text passwords never stored.\n\nSign in form allows for user authentication using either username or email input. Database is then queried for each parameter and verifies the user after unsalting the password digest stored in database.\n\n```ruby\n# User model\nvalidates :password, length: { minimum: 8, allow_nil: true }\n\ndef password=(password)\n  @password = password\n  self.password_digest = BCrypt::Password.create(password)\nend\n\ndef is_password?(password)\n  BCrypt::Password.new(self.password_digest).is_password?(password)\nend\n\ndef self.find_by_credentials(user_sign_in, password)\n  @user = User.find_by(username: user_sign_in)\n  @user = User.find_by(email: user_sign_in) if @user.nil?\n  return @user if @user \u0026\u0026 @user.is_password?(password)\n  nil\nend\n```\n\n### Frontend\n\nWhenevernote's frontend was built using the React framework and Redux cycle. These design choices were made so there would be an unidirectional data flow and all necessary data is stored in one source.\n\n#### Dependencies\n\n- Node package manager (npm) was used to install and save frontend dependencies.\n- jQuery is used to make AJAX requests to the backend Rails API.\n- Webpack was used to bundle all the reqeuired JavaScript files and ensure they are loaded in the correct order.\n- Draft.js is used in the notes editing component allowing rich text features.\n- Other frontend dependencies are React-DOM, React Router, Provider, React Mixin, and Babel for transpiling JSX into JavaScript for the browser.\n\nAfter the Notes index component mounts it fetches all notes belonging to the currently signed in user, achieved using associations in the Rails back end. Notes are sorted by last updated time using a selector before being returned to the component. Notes index will asynchronously wait for the return of successful promise then load the most recent note into the detail view.\n\n![Rich Text Editing](./docs/screenshots/rich-text.gif)\n\nRich Text Editing in the Note Detail view\n\n\n```javascript\n// Sorting of notes into most recently updated\nexport const notesByUpdated = ({ notes }) =\u003e {\n  const allNotes = values(notes)\n  return allNotes.sort((a, b) =\u003e (new Date(b.updated_at) - new Date(a.updated_at)))\n}\n\n// Notes Index fetches notes then loads the most recent\ncomponentDidMount() {\n  this.props.fetchNotes().then(() =\u003e {\n    this.props.history.push(`/notes/${this.props.notes[0].id}`)\n  })\n}\n```\nSession form designed to be used for both signing in and signing up for site. Process form method determined by current route inside the component containter then called when submit button is clicked.\n\n```javascript\nconst mapDispatchToProps = (dispatch, { location }) =\u003e {\n  const formType = location.pathname\n  const processForm = (formType === '/signup/') ? signup : signin\n\n  return {\n    processForm: user =\u003e dispatch(processForm(user)),\n    clearErrors: () =\u003e dispatch(clearErrors())\n  }\n}\n```\n\nNotebook Index component designed for use in multiple locations in the application. When in the sidebar modal, the click will cause a redirect to show all notes within selected notebook. Clicking an index item inside of the Note detail page will move the current note into the selected notebook.\n\nThe component when created is assigned a callback 'type' passed down as a prop to each index item. This callback type determines the action performed when a user clicks on the element. Notes count is a boolean setting whether or not to display note count in index view.\n\n```javascript\n// Notebook Index created in Note detail toolbar\n\u003cNotebooksIndex\n  notesCount={false}\n  iiCallback=\"assign\"\n  note={this.props.note}\n  updateNote={this.props.updateNote}\n/\u003e\n\n// Callback action called upon click either redirecting or updating of note\ncallbackAction() {\n  if (this.props.iiCallback === 'link') {\n    this.props.history.push(`/notebook/${this.props.notebook.id}/notes`)\n  } else if (this.props.iiCallback === 'assign') {\n    const newNotebook = { notebook_id : this.props.notebook.id }\n    const updatedNote = merge({}, this.props.note, newNotebook)\n\n    this.props.updateNote(updatedNote)\n  }\n}\n\nrender() {\n  const { title } = this.props.notebook\n\n  return (\n    \u003cli onClick={this.callbackAction} className=\"notebooks-ii\"\u003e\n      \u003ch4 className=\"nb-ii-child\"\u003e{title}\u003c/h4\u003e\n      {this.notesCount()}\n    \u003c/li\u003e\n  )\n}\n```\n\nNoteDetail component mounts then loads note to display based off the current route and sets an interval that calls the auto save method. Auto save checks the current note for any changes made by user to the title or body before sending PATCH request to save note, preventing the constant firing off of API requests.\n\n```javascript\ncomponentDidMount() {\n  this.props.fetchSingleNote(this.props.match.params.noteId).then(() =\u003e {\n    this.props.fetchSingleNotebook(this.props.note.notebook_id)\n    this.convertFromDB(this.props.note)\n  })\n\n  this.setInterval(() =\u003e {\n    this.autoSave()\n  }, 5000)\n}\n\nautoSave() {\n  const noteBody = convertToRaw(this.state.editorState.getCurrentContent())\n  const body = JSON.stringify(noteBody)\n\n  if ((this.state.title !== this.props.note.title) || (body !== this.props.note.body)) {\n    const note = {\n      body,\n      id: this.state.id,\n      title: this.state.title,\n      notebook_id: this.state.notebook_id,\n    }\n    this.props.updateNote(note);\n  }\n}\n```\n\n![Notebook View](./docs/screenshots/notebooks.gif)\n\nNotebook creation and note filing\n\n### Future Implementations\n\nI would like to return to development of Whenevernote when time permits and implement more features including:\n\n- Searching of notes, notebooks, and tags\n- Multiple user sessions\n- Add reminders and shortcuts to notes\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerreichle%2FWhenevernote","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftylerreichle%2FWhenevernote","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerreichle%2FWhenevernote/lists"}