├── rails ├── import │ └── .keep ├── log │ └── .keep ├── public │ └── .gitkeep ├── .ruby-version ├── app │ ├── models │ │ ├── concerns │ │ │ └── .keep │ │ └── entry.rb │ ├── controllers │ │ ├── concerns │ │ │ └── .keep │ │ ├── sessions_controller.rb │ │ ├── plans_controller.rb │ │ ├── application_controller.rb │ │ ├── users_controller.rb │ │ └── entries_controller.rb │ ├── serializers │ │ ├── entry_serializer.rb │ │ └── user_serializer.rb │ ├── views │ │ ├── user_mailer │ │ │ ├── export_entries.text.erb │ │ │ └── export_entries.html.erb │ │ ├── layouts │ │ │ ├── entry_mailer.text.erb │ │ │ └── entry_mailer.html.erb │ │ └── entry_mailer │ │ │ ├── welcome.text.erb │ │ │ ├── daily.text.erb │ │ │ ├── welcome.html.erb │ │ │ └── daily.html.erb │ ├── helpers │ │ └── entries_helper.rb │ └── mailers │ │ ├── user_mailer.rb │ │ └── entry_mailer.rb ├── vendor │ └── assets │ │ ├── javascripts │ │ └── .keep │ │ └── stylesheets │ │ └── .keep ├── .rspec ├── config │ ├── initializers │ │ ├── figaro.rb │ │ ├── session_store.rb │ │ ├── cookies_serializer.rb │ │ ├── hirefire.rb │ │ ├── mime_types.rb │ │ ├── stripe.rb │ │ ├── filter_parameter_logging.rb │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── email.rb │ │ ├── wrap_parameters.rb │ │ └── inflections.rb │ ├── environment.rb │ ├── boot.rb │ ├── database.yml │ ├── application.example.yml │ ├── puma.rb │ ├── locales │ │ ├── en.yml │ │ └── devise.en.yml │ ├── secrets.yml │ ├── routes.rb │ ├── application.rb │ └── environments │ │ ├── development.rb │ │ ├── test.rb │ │ └── production.rb ├── Procfile ├── bin │ ├── bundle │ ├── rake │ ├── delayed_job │ ├── rails │ ├── spring │ └── setup ├── db │ ├── migrate │ │ ├── 20141125163856_user_add_trial_end.rb │ │ ├── 20141125162240_user_add_last_email_sent.rb │ │ ├── 20141125161431_user_add_emails_sent.rb │ │ ├── 20141108213612_add_authentication_token_to_users.rb │ │ ├── 20141221035442_add_previous_email_flag.rb │ │ ├── 1_enable_extensions.rb │ │ ├── 20141109011650_create_entries.rb │ │ ├── 20141109001231_add_base_user_fields.rb │ │ ├── 20141122185234_create_delayed_jobs.rb │ │ └── 20141108212318_devise_create_users.rb │ ├── seeds.rb │ └── schema.rb ├── Procfile.local ├── deploy.sh ├── lib │ ├── development_mail_interceptor.rb │ └── tasks │ │ └── entry.rake ├── Rakefile ├── config.ru ├── spec │ ├── models │ │ ├── entry_spec.rb │ │ └── user_spec.rb │ ├── rails_helper.rb │ └── spec_helper.rb ├── .gitignore ├── Gemfile └── README.rdoc ├── ember ├── public │ ├── .gitkeep │ ├── robots.txt │ ├── favicon.ico │ ├── assets │ │ ├── images │ │ │ ├── logo.jpg │ │ │ ├── logo.png │ │ │ └── home │ │ │ │ ├── screen_1.jpg │ │ │ │ ├── screen_2.jpg │ │ │ │ ├── screen_3.jpg │ │ │ │ ├── screen_4.jpg │ │ │ │ └── screen_5.jpg │ │ └── patterns │ │ │ ├── halftone.png │ │ │ ├── ricepaper2.png │ │ │ ├── groovepaper.png │ │ │ ├── ricepaper2_@2X.png │ │ │ ├── low-contrast-linen.png │ │ │ ├── subtle_white_feathers.png │ │ │ └── subtle_white_feathers_@2X.png │ └── crossdomain.xml ├── vendor │ ├── .gitkeep │ └── js │ │ └── pikaday.jquery.js ├── app │ ├── helpers │ │ ├── .gitkeep │ │ ├── pretty-day.js │ │ ├── pretty-month.js │ │ ├── showdown-html.js │ │ ├── pretty-time.js │ │ └── showdown-with-search.js │ ├── models │ │ ├── .gitkeep │ │ ├── entry.js │ │ └── user.js │ ├── routes │ │ ├── .gitkeep │ │ ├── password-reset.js │ │ ├── importer.js │ │ ├── about.js │ │ ├── login.js │ │ ├── protected.js │ │ ├── register.js │ │ ├── plans.js │ │ ├── settings.js │ │ ├── index.js │ │ ├── entries │ │ │ ├── edit.js │ │ │ ├── new.js │ │ │ ├── index.js │ │ │ └── show.js │ │ ├── plan-required.js │ │ └── application.js │ ├── styles │ │ ├── .gitkeep │ │ ├── errors.styl │ │ ├── notify.styl │ │ ├── app.styl │ │ ├── animations.styl │ │ ├── variables.styl │ │ ├── utility.styl │ │ ├── media_queries.styl │ │ ├── bootstrap_custom.styl │ │ ├── entries.styl │ │ ├── users.styl │ │ ├── pikaday.styl │ │ ├── home.styl │ │ └── github-ribbon.styl │ ├── templates │ │ ├── .gitkeep │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── calendar-date-picker.hbs │ │ ├── slider.hbs │ │ ├── entries │ │ │ ├── edit.hbs │ │ │ ├── new.hbs │ │ │ ├── list.hbs │ │ │ ├── _show.hbs │ │ │ ├── _list_item.hbs │ │ │ ├── show.hbs │ │ │ ├── form.hbs │ │ │ └── index.hbs │ │ ├── entries.hbs │ │ ├── canceled.hbs │ │ ├── login.hbs │ │ ├── register.hbs │ │ ├── widgets │ │ │ └── large-logo.hbs │ │ ├── _footer.hbs │ │ ├── users │ │ │ ├── email-time.hbs │ │ │ ├── register-form.hbs │ │ │ └── login-form.hbs │ │ ├── password-reset.hbs │ │ ├── application.hbs │ │ ├── passwords │ │ │ ├── reset-start-form.hbs │ │ │ └── reset-finish-form.hbs │ │ ├── home-nav.hbs │ │ ├── importer.hbs │ │ ├── site-nav.hbs │ │ ├── plans.hbs │ │ ├── index.hbs │ │ └── about.hbs │ ├── views │ │ ├── .gitkeep │ │ ├── entries │ │ │ ├── form.js │ │ │ └── list.js │ │ ├── index.js │ │ ├── site-nav.js │ │ ├── textarea-autosize.js │ │ ├── users │ │ │ └── email-time.js │ │ └── slider.js │ ├── components │ │ ├── .gitkeep │ │ └── calendar-date-picker.js │ ├── controllers │ │ ├── .gitkeep │ │ ├── user.js │ │ ├── index.js │ │ ├── login.js │ │ ├── entries │ │ │ ├── index.js │ │ │ └── show.js │ │ ├── register.js │ │ ├── password-reset.js │ │ ├── plans.js │ │ ├── importer.js │ │ └── settings.js │ ├── serializers │ │ └── application.js │ ├── mixins │ │ └── reset-scroll.js │ ├── app.js │ ├── transforms │ │ ├── object.js │ │ └── array.js │ ├── router.js │ ├── index.html │ └── adapters │ │ └── application.js ├── tests │ ├── unit │ │ └── .gitkeep │ ├── helpers │ │ ├── resolver.js │ │ └── start-app.js │ ├── test-helper.js │ ├── index.html │ └── .jshintrc ├── .bowerrc ├── .gitignore ├── testem.json ├── .ember-cli ├── .travis.yml ├── .editorconfig ├── bower.json ├── .jshintrc ├── Brocfile.js ├── package.json ├── README.md └── config │ └── environment.js ├── docs ├── logo.png └── feature_frame.sketch ├── .gitignore └── README.md /rails/import/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/styles/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/templates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/controllers/user.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails/.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.1.4 -------------------------------------------------------------------------------- /rails/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails/vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails/vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rails/.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /ember/app/templates/slider.hbs: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /ember/app/templates/entries/edit.hbs: -------------------------------------------------------------------------------- 1 | {{partial "entries/form"}} -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/docs/logo.png -------------------------------------------------------------------------------- /ember/app/styles/errors.styl: -------------------------------------------------------------------------------- 1 | .d-error-box 2 | p 3 | color $red -------------------------------------------------------------------------------- /ember/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /ember/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /rails/config/initializers/figaro.rb: -------------------------------------------------------------------------------- 1 | # Figaro.require_keys("PG_PUBLIC_KEY", "PG_PRIVATE_KEY") -------------------------------------------------------------------------------- /rails/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.session_store :disabled -------------------------------------------------------------------------------- /docs/feature_frame.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/docs/feature_frame.sketch -------------------------------------------------------------------------------- /ember/app/templates/entries/new.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{view "entries/form"}} 3 |
-------------------------------------------------------------------------------- /ember/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/favicon.ico -------------------------------------------------------------------------------- /rails/Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C config/puma.rb 2 | worker: bundle exec rake jobs:work -------------------------------------------------------------------------------- /ember/app/templates/entries.hbs: -------------------------------------------------------------------------------- 1 | {{view "site-nav"}} 2 | 3 |
4 | {{outlet}} 5 |
-------------------------------------------------------------------------------- /ember/app/routes/password-reset.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Route.extend({}); 4 | -------------------------------------------------------------------------------- /ember/app/styles/notify.styl: -------------------------------------------------------------------------------- 1 | .ember-notify 2 | .close 3 | position absolute 4 | top 14px 5 | right 15px -------------------------------------------------------------------------------- /ember/public/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/images/logo.jpg -------------------------------------------------------------------------------- /ember/public/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/images/logo.png -------------------------------------------------------------------------------- /ember/public/assets/patterns/halftone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/patterns/halftone.png -------------------------------------------------------------------------------- /ember/app/routes/importer.js: -------------------------------------------------------------------------------- 1 | import PlanRequiredRoute from "./plan-required"; 2 | 3 | export default PlanRequiredRoute.extend({}); -------------------------------------------------------------------------------- /ember/app/serializers/application.js: -------------------------------------------------------------------------------- 1 | import DS from "ember-data"; 2 | 3 | export default DS.ActiveModelSerializer.extend({ 4 | }); -------------------------------------------------------------------------------- /ember/public/assets/patterns/ricepaper2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/patterns/ricepaper2.png -------------------------------------------------------------------------------- /ember/public/assets/images/home/screen_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/images/home/screen_1.jpg -------------------------------------------------------------------------------- /ember/public/assets/images/home/screen_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/images/home/screen_2.jpg -------------------------------------------------------------------------------- /ember/public/assets/images/home/screen_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/images/home/screen_3.jpg -------------------------------------------------------------------------------- /ember/public/assets/images/home/screen_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/images/home/screen_4.jpg -------------------------------------------------------------------------------- /ember/public/assets/images/home/screen_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/images/home/screen_5.jpg -------------------------------------------------------------------------------- /ember/public/assets/patterns/groovepaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/patterns/groovepaper.png -------------------------------------------------------------------------------- /ember/app/templates/canceled.hbs: -------------------------------------------------------------------------------- 1 |

Canceled Page

2 | 3 | {{#link-to "plans"}}Re-subscribe{{/link-to}} 4 | 5 | {{partial "site_nav"}} -------------------------------------------------------------------------------- /ember/public/assets/patterns/ricepaper2_@2X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/patterns/ricepaper2_@2X.png -------------------------------------------------------------------------------- /ember/public/assets/patterns/low-contrast-linen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/patterns/low-contrast-linen.png -------------------------------------------------------------------------------- /ember/public/assets/patterns/subtle_white_feathers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/patterns/subtle_white_feathers.png -------------------------------------------------------------------------------- /rails/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /ember/public/assets/patterns/subtle_white_feathers_@2X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i/dayjot/master/ember/public/assets/patterns/subtle_white_feathers_@2X.png -------------------------------------------------------------------------------- /rails/app/serializers/entry_serializer.rb: -------------------------------------------------------------------------------- 1 | class EntrySerializer < ActiveModel::Serializer 2 | attributes :id, :body, :entry_date, :source, :user_id 3 | end 4 | -------------------------------------------------------------------------------- /ember/app/routes/about.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import ResetScroll from "dayjot/mixins/reset-scroll"; 3 | 4 | export default Ember.Route.extend(ResetScroll, {}); -------------------------------------------------------------------------------- /rails/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | /rails/public/* 4 | /rails/.ruby-gemset 5 | /rails/config/application.yml 6 | /rails/dump.rdb 7 | rails/.rubocop.yml 8 | rails/dump.rdb 9 | rails/.foreman -------------------------------------------------------------------------------- /ember/app/helpers/pretty-day.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Handlebars.makeBoundHelper(function(time) { 4 | return moment(time).utc().format("dddd"); 5 | }); -------------------------------------------------------------------------------- /rails/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! -------------------------------------------------------------------------------- /rails/config/initializers/hirefire.rb: -------------------------------------------------------------------------------- 1 | HireFire::Resource.configure do |config| 2 | config.dyno(:worker) do 3 | HireFire::Macro::Delayed::Job.queue(mapper: :active_record) 4 | end 5 | end -------------------------------------------------------------------------------- /rails/db/migrate/20141125163856_user_add_trial_end.rb: -------------------------------------------------------------------------------- 1 | class UserAddTrialEnd < ActiveRecord::Migration 2 | def change 3 | add_column :users, :trial_end, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ember/app/mixins/reset-scroll.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Mixin.create({ 4 | activate: function() { 5 | this._super(); 6 | window.scrollTo(0,0); 7 | } 8 | }); -------------------------------------------------------------------------------- /ember/app/helpers/pretty-month.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Handlebars.makeBoundHelper(function(time) { 4 | return moment(time, "YYYY-M").utc().format("MMMM YYYY"); 5 | }); -------------------------------------------------------------------------------- /rails/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /rails/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /rails/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /rails/db/migrate/20141125162240_user_add_last_email_sent.rb: -------------------------------------------------------------------------------- 1 | class UserAddLastEmailSent < ActiveRecord::Migration 2 | def change 3 | add_column :users, :last_reminder_sent_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /rails/bin/delayed_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /rails/db/migrate/20141125161431_user_add_emails_sent.rb: -------------------------------------------------------------------------------- 1 | class UserAddEmailsSent < ActiveRecord::Migration 2 | def change 3 | add_column :users, :reminder_emails_sent, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ember/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /tmp 3 | /node_modules 4 | /bower_components 5 | /.sass-cache 6 | /connect.lock 7 | /coverage/* 8 | /libpeerconnection.log 9 | npm-debug.log 10 | testem.log 11 | .DS_Store 12 | /parse/* -------------------------------------------------------------------------------- /rails/db/migrate/20141108213612_add_authentication_token_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAuthenticationTokenToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :authentication_token, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /rails/db/migrate/20141221035442_add_previous_email_flag.rb: -------------------------------------------------------------------------------- 1 | class AddPreviousEmailFlag < ActiveRecord::Migration 2 | def change 3 | add_column :users, :include_email_memory, :boolean, default: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ember/app/views/entries/form.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.View.extend({ 4 | templateName: 'entries/form', 5 | 6 | didInsertElement: function(){ 7 | this.$('textarea').focus(); 8 | } 9 | }); -------------------------------------------------------------------------------- /ember/testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html", 4 | "launch_in_ci": [ 5 | "PhantomJS" 6 | ], 7 | "launch_in_dev": [ 8 | "PhantomJS", 9 | "Chrome" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/templates/components/calendar-date-picker.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{input type='text' readonly='true' value=value class='form-control'}} -------------------------------------------------------------------------------- /ember/app/routes/login.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Route.extend({ 4 | activate: function() { 5 | if (this.get('session').isAuthenticated) { 6 | this.transitionTo('entries'); 7 | } 8 | } 9 | }); -------------------------------------------------------------------------------- /rails/Procfile.local: -------------------------------------------------------------------------------- 1 | web: rails s --port $PORT 2 | worker: bundle exec rake jobs:work 3 | ember: sh -c 'cd ../ember && exec ember s --environment development --port 4300 --live-reload-port 31550 --output-path ../rails/public' -------------------------------------------------------------------------------- /ember/app/routes/protected.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Route.extend({ 4 | beforeModel: function() { 5 | if (!this.get('session.isAuthenticated')) { 6 | this.transitionTo('index'); 7 | } 8 | } 9 | }); -------------------------------------------------------------------------------- /ember/app/routes/register.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Route.extend({ 4 | activate: function() { 5 | if (this.get('session').isAuthenticated) { 6 | this.transitionTo('entries'); 7 | } 8 | } 9 | }); -------------------------------------------------------------------------------- /rails/config/initializers/stripe.rb: -------------------------------------------------------------------------------- 1 | Rails.configuration.stripe = { 2 | :publishable_key => ENV['STRIPE_PUBLISHABLE_KEY'], 3 | :secret_key => ENV['STRIPE_SECRET_KEY'] 4 | } 5 | 6 | Stripe.api_key = Rails.configuration.stripe[:secret_key] -------------------------------------------------------------------------------- /rails/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ../ember && ember build --environment production --output-path ../rails/public 4 | cd ../rails 5 | git add . 6 | git commit -am "Empty commit for Heroku deployment" --allow-empty 7 | git push heroku master -------------------------------------------------------------------------------- /rails/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /rails/lib/development_mail_interceptor.rb: -------------------------------------------------------------------------------- 1 | class DevelopmentMailInterceptor 2 | def self.delivering_email(message) 3 | message.subject = "#{message.to} #{message.subject}" 4 | message.to = ENV["DEVELOPMENT_INTERCEPT_EMAIL"] 5 | end 6 | end 7 | 8 | -------------------------------------------------------------------------------- /rails/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /rails/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /ember/app/templates/login.hbs: -------------------------------------------------------------------------------- 1 | {{partial "home-nav"}} 2 |
3 |
4 | 5 |
6 |

Login to DayJot

7 | {{partial "users/login-form"}} 8 |
9 |
10 |
-------------------------------------------------------------------------------- /ember/app/routes/plans.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import ResetScroll from "dayjot/mixins/reset-scroll"; 3 | 4 | export default Ember.Route.extend(ResetScroll, { 5 | setupController: function(controller) { 6 | controller.set('model', this.get('session.currentUser')); 7 | } 8 | }); -------------------------------------------------------------------------------- /rails/app/views/user_mailer/export_entries.text.erb: -------------------------------------------------------------------------------- 1 | Hi there, 2 | 3 | Attached to this email is a text file containing all of your entries on DayJot.com. 4 | 5 | If you have any questions or concerns, please don't hesitate to repy to this email. 6 | 7 | Sincerely, 8 | Your friendly DayJot Robot. -------------------------------------------------------------------------------- /ember/app/templates/register.hbs: -------------------------------------------------------------------------------- 1 | {{partial "home-nav"}} 2 |
3 |
4 | 5 |
6 |

Sign Up for DayJot

7 | {{partial "users/register-form"}} 8 |
9 |
10 |
-------------------------------------------------------------------------------- /rails/db/migrate/1_enable_extensions.rb: -------------------------------------------------------------------------------- 1 | class EnableExtensions < ActiveRecord::Migration 2 | def up 3 | enable_extension :hstore 4 | enable_extension 'uuid-ossp' 5 | end 6 | 7 | def down 8 | disable_extension :hstore 9 | disable_extension 'uuid-ossp' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ember/app/templates/widgets/large-logo.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/helpers/showdown-html.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Handlebars.makeBoundHelper(function(value) { 4 | if (typeof value === 'string') { 5 | var converter = new Showdown.converter(); 6 | return new Ember.Handlebars.SafeString(converter.makeHtml(value)); 7 | } 8 | }); -------------------------------------------------------------------------------- /ember/app/routes/settings.js: -------------------------------------------------------------------------------- 1 | import ProtectedRoute from "./protected"; 2 | import ResetScroll from "dayjot/mixins/reset-scroll"; 3 | 4 | export default ProtectedRoute.extend(ResetScroll, { 5 | setupController: function(controller) { 6 | controller.set('model', this.get('session.currentUser')); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /ember/app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import ResetScroll from "dayjot/mixins/reset-scroll"; 3 | 4 | export default Ember.Route.extend(ResetScroll, { 5 | beforeModel: function() { 6 | if (this.get('session.isAuthenticated')) { 7 | this.transitionTo('entries'); 8 | } 9 | } 10 | }); -------------------------------------------------------------------------------- /ember/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": true 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/templates/_footer.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /rails/app/serializers/user_serializer.rb: -------------------------------------------------------------------------------- 1 | class UserSerializer < ActiveModel::Serializer 2 | attributes :id, :email, :email_times, :entry_months, :last_export_time, 3 | :plan, :plan_started, :plan_canceled, :plan_status, :trial_end, :time_zone, :status, :entry_months, 4 | :created_at, :include_email_memory 5 | end 6 | -------------------------------------------------------------------------------- /rails/app/helpers/entries_helper.rb: -------------------------------------------------------------------------------- 1 | module EntriesHelper 2 | def markdown(text) 3 | options = {hard_wrap: true, filter_html: true, autolink: true, no_intraemphasis: true, fenced_code: true, gh_blockcode: true} 4 | markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options) 5 | markdown.render(text).html_safe 6 | end 7 | end -------------------------------------------------------------------------------- /ember/.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | 4 | sudo: false 5 | 6 | cache: 7 | directories: 8 | - node_modules 9 | 10 | before_install: 11 | - "npm config set spin false" 12 | - "npm install -g npm@^2" 13 | 14 | install: 15 | - npm install -g bower 16 | - npm install 17 | - bower install 18 | 19 | script: 20 | - npm test 21 | -------------------------------------------------------------------------------- /ember/app/helpers/pretty-time.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Handlebars.makeBoundHelper(function(time) { 4 | if (!time) { 5 | return ""; 6 | } 7 | 8 | if (time.iso) { 9 | return moment(time.iso).utc().format("MMMM Do, YYYY"); 10 | } else { 11 | return moment(time).utc().format("MMMM Do, YYYY"); 12 | } 13 | }); -------------------------------------------------------------------------------- /rails/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /rails/app/views/user_mailer/export_entries.html.erb: -------------------------------------------------------------------------------- 1 |

Hi there,

2 | 3 |

4 | Attached to this email is a text file containing all of your entries on DayJot.com. 5 |

6 | 7 |

8 | If you have any questions or concerns, please don't hesitate to repy to this email. 9 |

10 | 11 |

12 | Sincerely, 13 |
14 | Your friendly DayJot Robot. 15 |

-------------------------------------------------------------------------------- /rails/config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: postgresql 3 | encoding: unicode 4 | database: dayjot_development 5 | pool: 5 6 | username: <%= ENV["DB_USERNAME"] %> 7 | password: <%= ENV["DB_PASSWORD"] %> 8 | 9 | test: 10 | adapter: postgresql 11 | database: dayjot_test 12 | username: <%= ENV["DB_USERNAME"] %> 13 | password: <%= ENV["DB_PASSWORD"] %> 14 | -------------------------------------------------------------------------------- /ember/app/routes/entries/edit.js: -------------------------------------------------------------------------------- 1 | import PlanRequiredRoute from "../plan-required"; 2 | 3 | export default PlanRequiredRoute.extend({ 4 | controllerName: 'entries.show', 5 | 6 | setupController: function(controller, model) { 7 | this._super(controller, model); 8 | controller.set('entryDatePretty', moment(model.get('entryDate')).format("MMMM Do, YYYY")); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /ember/app/styles/app.styl: -------------------------------------------------------------------------------- 1 | @import "variables" 2 | 3 | @import "github-ribbon" 4 | @import "bootstrap" 5 | @import "bootstrap_custom" 6 | @import "pikaday" 7 | @import "jquery-ui" 8 | @import "notify" 9 | 10 | @import "utility" 11 | @import "structure" 12 | @import "animations" 13 | @import "errors" 14 | @import "home" 15 | @import "users" 16 | @import "entries" 17 | // @import "media_queries" -------------------------------------------------------------------------------- /ember/app/views/index.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.View.extend({ 4 | actions: { 5 | activateFeatureTab: function(which) { 6 | this.$('.d-feature-container img:nth-child('+which+')').addClass('on').siblings().removeClass('on'); 7 | this.$('.d-feature-container li:nth-child('+which+')').addClass('on').siblings().removeClass('on'); 8 | } 9 | } 10 | }); -------------------------------------------------------------------------------- /rails/app/views/layouts/entry_mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | 3 | --- 4 | 5 | Reply with entry for date: <%= Time.now.in_time_zone(@user.time_zone).strftime("%Y-%m-%d") %>. 6 | 7 | You can always send a new email to <%= "#{@user.email_key}@post.dayjot.com" %> to create a new entry. 8 | The entry will be created for the current date, and any subsequent emails within the same day will be appended to that entry. -------------------------------------------------------------------------------- /rails/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | 6 | DelayedJobWeb.enable :sessions 7 | 8 | if Rails.env.production? 9 | DelayedJobWeb.use Rack::Auth::Basic do |username, password| 10 | username == ENV['ADMIN_USER'] && password == ENV['ADMIN_PASSWORD'] 11 | end 12 | end -------------------------------------------------------------------------------- /ember/app/models/entry.js: -------------------------------------------------------------------------------- 1 | import DS from "ember-data"; 2 | 3 | export default DS.Model.extend({ 4 | body: DS.attr('string'), 5 | 6 | entryDate: DS.attr(), 7 | occurredAtYearMonth: DS.attr('string', {readOnly: true}), 8 | 9 | createdAt: DS.attr('date'), 10 | updatedAt: DS.attr('date'), 11 | 12 | timestamp: function() { 13 | return moment(this.get('entryDate')).unix(); 14 | }.property('entryDate') 15 | }); -------------------------------------------------------------------------------- /ember/app/templates/users/email-time.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{view "select" content=view.times 4 | optionLabelPath="content.name" 5 | optionValuePath="content.value" 6 | prompt=view.label 7 | value=view.currentTime 8 | class="form-control"}} 9 |
-------------------------------------------------------------------------------- /rails/config/application.example.yml: -------------------------------------------------------------------------------- 1 | DB_USERNAME: "" 2 | DB_PASSWORD: "" 3 | 4 | ADMIN_USER: "admin" 5 | ADMIN_PASSWORD: "1234" 6 | 7 | STRIPE_PUBLISHABLE_KEY: "pk_test_123" 8 | STRIPE_SECRET_KEY: "sk_test_123" 9 | 10 | MAILGUN_SMTP_PORT: "XXX" 11 | MAILGUN_SMTP_SERVER: "XXX" 12 | MAILGUN_SMTP_LOGIN: "XXX" 13 | MAILGUN_SMTP_PASSWORD: "XXX" 14 | MAILGUN_DOMAIN: "XXX" 15 | 16 | DEVELOPMENT_INTERCEPT_EMAIL: "foo@example.com" -------------------------------------------------------------------------------- /ember/app/templates/password-reset.hbs: -------------------------------------------------------------------------------- 1 | {{partial "home-nav"}} 2 |
3 |
4 |
5 |

Reset Password

6 | 7 | {{#if reset_password_token}} 8 | {{partial "passwords/reset-finish-form"}} 9 | {{else}} 10 | {{partial "passwords/reset-start-form"}} 11 | {{/if}} 12 |
13 |
14 |
-------------------------------------------------------------------------------- /rails/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Precompile additional assets. 7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 8 | # Rails.application.config.assets.precompile += %w( search.js ) 9 | -------------------------------------------------------------------------------- /rails/db/migrate/20141109011650_create_entries.rb: -------------------------------------------------------------------------------- 1 | class CreateEntries < ActiveRecord::Migration 2 | def change 3 | create_table :entries, id: :uuid do |t| 4 | t.binary :encrypted_body 5 | t.text :body 6 | t.date :entry_date 7 | t.string :source, default: "web" 8 | t.uuid :user_id 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :entries, [:user_id, :entry_date] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /ember/app/routes/entries/new.js: -------------------------------------------------------------------------------- 1 | import PlanRequiredRoute from "../plan-required"; 2 | 3 | export default PlanRequiredRoute.extend({ 4 | controllerName: 'entries.show', 5 | 6 | model: function() { 7 | return this.store.createRecord('entry'); 8 | }, 9 | 10 | setupController: function(controller, model) { 11 | this._super(controller, model); 12 | controller.set('entryDatePretty', moment().format("MMMM Do, YYYY")); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /rails/spec/models/entry_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Entry, :type => :model do 4 | describe "validations" do 5 | 6 | it { should belong_to(:user).class_name('User') } 7 | 8 | it { should validate_presence_of(:user) } 9 | it { should validate_presence_of(:entry_date) } 10 | it { should validate_presence_of(:body) } 11 | 12 | it { should ensure_length_of(:body).is_at_most(50000) } 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /rails/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /rails/app/views/layouts/entry_mailer.html.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | 3 |
4 | 5 |

6 | Reply with entry for date: <%= Time.now.in_time_zone(@user.time_zone).strftime("%Y-%m-%d") %>. 7 |

8 | 9 |

10 | You can always send a new email to <%= "#{@user.email_key}@post.dayjot.com" %> to create a new entry. 11 |
12 | The entry will be created for the current date, and any subsequent emails within the same day will be appended to that entry. 13 |

-------------------------------------------------------------------------------- /rails/spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe User, :type => :model do 4 | describe "creation" do 5 | before do 6 | @user = User.create(:email => "user@localhist", :password => "password123") 7 | end 8 | 9 | it "sets default settings" do 10 | expect(@user.trial_end).not_to eq nil 11 | end 12 | 13 | it "has many entries" do 14 | should have_many(:entries).dependent(:destroy) 15 | end 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /rails/app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < Devise::SessionsController 2 | def create 3 | self.resource = warden.authenticate!(auth_options) 4 | sign_in(resource_name, resource) 5 | data = { 6 | user_token: resource.authentication_token, 7 | user_email: resource.email 8 | } 9 | render json: data, status: 201 10 | end 11 | 12 | def destroy 13 | sign_out :user 14 | render json: {}, status: :accepted 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /rails/config/initializers/email.rb: -------------------------------------------------------------------------------- 1 | ActionMailer::Base.smtp_settings = { 2 | :port => ENV['MAILGUN_SMTP_PORT'], 3 | :address => ENV['MAILGUN_SMTP_SERVER'], 4 | :user_name => ENV['MAILGUN_SMTP_LOGIN'], 5 | :password => ENV['MAILGUN_SMTP_PASSWORD'], 6 | :domain => ENV['MAILGUN_DOMAIN'], 7 | :authentication => :plain, 8 | } 9 | ActionMailer::Base.delivery_method = :smtp 10 | 11 | if Rails.env.development? 12 | Mail.register_interceptor(DevelopmentMailInterceptor) 13 | end -------------------------------------------------------------------------------- /ember/app/routes/plan-required.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | // import Notify from 'ember-notify'; 3 | 4 | export default Ember.Route.extend({ 5 | beforeModel: function() { 6 | if (!this.get('session.isAuthenticated')) { 7 | this.transitionTo('index'); 8 | } //else { 9 | // if (this.get('session.currentUser.mustSubscribe')) { 10 | // this.transitionTo('plans'); 11 | // Notify.error('Become an active member to view that page.'); 12 | // } 13 | // } 14 | } 15 | }); -------------------------------------------------------------------------------- /ember/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | 8 | document.write('
'); 9 | 10 | QUnit.config.urlConfig.push({ id: 'nocontainer', label: 'Hide container'}); 11 | var containerVisibility = QUnit.urlParams.nocontainer ? 'hidden' : 'visible'; 12 | document.getElementById('ember-testing-container').style.visibility = containerVisibility; 13 | -------------------------------------------------------------------------------- /ember/app/helpers/showdown-with-search.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Handlebars.makeBoundHelper(function(value, search) { 4 | if (typeof value === 'string') { 5 | var converter = new Showdown.converter(), 6 | html = converter.makeHtml(value), 7 | reg = new RegExp(search, 'gi'), 8 | highlighted_html = html.replace(reg, function(str) { return ''+str+''; }); 9 | 10 | return new Ember.Handlebars.SafeString(highlighted_html); 11 | } 12 | }); -------------------------------------------------------------------------------- /ember/app/templates/entries/list.hbs: -------------------------------------------------------------------------------- 1 | {{#if length}} 2 |
3 | {{#each}} 4 | {{partial "entries/list_item" withActions=true}} 5 | {{/each}} 6 | 7 | {{#if endOfList}} 8 |
No More Entries
9 | {{else}} 10 |
Load More
11 | {{/if}} 12 |
13 | {{else}} 14 |
No Entries
15 | {{/if}} -------------------------------------------------------------------------------- /ember/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from 'dayjot/config/environment'; 5 | import Notify from 'ember-notify'; 6 | 7 | Ember.MODEL_FACTORY_INJECTIONS = true; 8 | 9 | var App = Ember.Application.extend({ 10 | modulePrefix: config.modulePrefix, 11 | podModulePrefix: config.podModulePrefix, 12 | Resolver: Resolver 13 | }); 14 | 15 | loadInitializers(App, config.modulePrefix); 16 | 17 | Notify.useBootstrap(); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /rails/app/views/entry_mailer/welcome.text.erb: -------------------------------------------------------------------------------- 1 | Welcome to DayJot, the simple, permanent journaling app. 2 | 3 | How's your day going? To get started, simply reply to this email with your first entry. 4 | 5 | A few notes: 6 | 7 | - Only you can read what you write, so there is no wrong way to do this. 8 | - You can reply to this email as many times as you like. Subsequent responses will be appended to your entry. 9 | - You can format your entry with Markdown, a simple markup syntax. 10 | 11 | If you have any questions, please don't hesitate to shoot an email to help@dayjot.com. -------------------------------------------------------------------------------- /ember/tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import Router from '../../router'; 4 | import config from '../../config/environment'; 5 | 6 | export default function startApp(attrs) { 7 | var App; 8 | 9 | var attributes = Ember.merge({}, config.APP); 10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 11 | 12 | Ember.run(function() { 13 | App = Application.create(attributes); 14 | App.setupForTesting(); 15 | App.injectTestHelpers(); 16 | }); 17 | 18 | return App; 19 | } 20 | -------------------------------------------------------------------------------- /rails/bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast 4 | # It gets overwritten when you run the `spring binstub` command 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m) 11 | ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) 12 | ENV["GEM_HOME"] = "" 13 | Gem.paths = ENV 14 | 15 | gem "spring", match[1] 16 | require "spring/binstub" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /ember/app/styles/animations.styl: -------------------------------------------------------------------------------- 1 | .fade-in 2 | animation 0.6s cubic-bezier(0.19, 1, 0.98, 1) 0s normal none 1 fadeIn 3 | -webkit-animation 0.6s cubic-bezier(0.19, 1, 0.98, 1) 0s normal none 1 fadeIn 4 | visibility visible !important 5 | 6 | @keyframes fadeIn { 7 | 0% { 8 | transform scale(0) 9 | opacity 0.0 10 | } 11 | 100% { 12 | transform scale(1) 13 | opacity 1 14 | } 15 | } 16 | 17 | @-webkit-keyframes fadeIn { 18 | 0% { 19 | -webkit-transform scale(0) 20 | opacity 0.0 21 | } 22 | 100% { 23 | -webkit-transform scale(1) 24 | opacity 1 25 | } 26 | } -------------------------------------------------------------------------------- /rails/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /ember/app/transforms/object.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import DS from "ember-data"; 3 | 4 | export default DS.Transform.extend({ 5 | deserialize: function(serialized) { 6 | if (Ember.typeOf(serialized) === "string") { 7 | return JSON.parse(serialized); 8 | } else if (serialized) { 9 | return serialized; 10 | } else { 11 | return {}; 12 | } 13 | }, 14 | 15 | serialize: function(deserialized) { 16 | var type = Ember.typeOf(deserialized); 17 | if (type === 'object') { 18 | return deserialized; 19 | } else { 20 | return {}; 21 | } 22 | } 23 | }); -------------------------------------------------------------------------------- /ember/app/styles/variables.styl: -------------------------------------------------------------------------------- 1 | // Structure 2 | 3 | $site-width = 960px 4 | $panel-max-width = 800px 5 | $base-padding = 40px 6 | $texture-url = "/assets/patterns/groovepaper.png" 7 | $contrast-texture-url = "/assets/patterns/low-contrast-linen.png" 8 | $header-spacing = 95px 9 | $footer-height = 55px 10 | 11 | 12 | // Colors 13 | 14 | $site-text-color = #333 15 | $body-bg-color = #fffffa 16 | 17 | $primary-color = #2980b9 18 | $secondary-color = #2980b9 + 40% 19 | $positive-color = #27ae60 20 | $negative-color = #c0392b 21 | $info-color = #3498db 22 | $action-color = #f39c12 23 | $highlight-color = #8e44ad 24 | $yellow = #f1c40f -------------------------------------------------------------------------------- /ember/app/transforms/array.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import DS from "ember-data"; 3 | 4 | export default DS.Transform.extend({ 5 | deserialize: function(serialized) { 6 | return (Ember.typeOf(serialized) === "array") ? serialized : []; 7 | }, 8 | 9 | serialize: function(deserialized) { 10 | var type = Ember.typeOf(deserialized); 11 | if (type === 'array') { 12 | return deserialized; 13 | } else if (type === 'string') { 14 | return deserialized.split(',').map(function(item) { 15 | return item.trim(); 16 | }); 17 | } else { 18 | return []; 19 | } 20 | } 21 | }); -------------------------------------------------------------------------------- /rails/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | 18 | # Ignore application configuration 19 | /config/application.yml 20 | 21 | /import/*.json -------------------------------------------------------------------------------- /ember/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{#if session.currentUser}} 4 | {{#unless session.currentUser.planActive}} 5 | 14 | {{/unless}} 15 | {{/if}} 16 | 17 | {{outlet}} 18 |
19 | 20 | {{partial "footer"}} -------------------------------------------------------------------------------- /rails/config/puma.rb: -------------------------------------------------------------------------------- 1 | workers Integer(ENV['PUMA_WORKERS'] || 3) 2 | threads Integer(ENV['MIN_THREADS'] || 1), Integer(ENV['MAX_THREADS'] || 16) 3 | 4 | preload_app! 5 | 6 | rackup DefaultRackup 7 | port ENV['PORT'] || 3000 8 | environment ENV['RACK_ENV'] || 'development' 9 | 10 | on_worker_boot do 11 | # worker specific setup 12 | ActiveSupport.on_load(:active_record) do 13 | config = ActiveRecord::Base.configurations[Rails.env] || 14 | Rails.application.config.database_configuration[Rails.env] 15 | config['pool'] = ENV['MAX_THREADS'] || 16 16 | ActiveRecord::Base.establish_connection(config) 17 | end 18 | end -------------------------------------------------------------------------------- /rails/app/views/entry_mailer/daily.text.erb: -------------------------------------------------------------------------------- 1 | Just reply to this email with your entry for <%= Time.now.in_time_zone(@user.time_zone).strftime("%B %-d") %>. 2 | 3 | --- 4 | 5 | <% if @user.include_email_memory %> 6 | <% if @show_entry %> 7 | <%= @entry.time_ago(@user) %> ago you wrote... 8 | 9 | <%= @entry.sanitized_body %> 10 | 11 | --- 12 | 13 | View Entry: <%= entry_url(@entry.entry_date) %> 14 | <% else %> 15 | Keep writing...once you have a few entries you'll see a blast from the past show up here! 16 | <% end %> 17 | <% end %> 18 | 19 | Past entries: https://dayjot.com/entries 20 | 21 | Unsubscribe: https://dayjot.com/settings -------------------------------------------------------------------------------- /ember/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.css] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.html] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | [*.md] 33 | trim_trailing_whitespace = false 34 | -------------------------------------------------------------------------------- /ember/app/styles/utility.styl: -------------------------------------------------------------------------------- 1 | .d-serif 2 | font-family 'Halant', serif 3 | input 4 | textarea 5 | font-family 'Halant', serif 6 | 7 | .d-list-none 8 | padding $base-padding 0 9 | 10 | .fadeout 11 | -webkit-transition opacity 0.5s ease-in-out !important 12 | -moz-transition opacity 0.5s ease-in-out !important 13 | -ms-transition opacity 0.5s ease-in-out !important 14 | -o-transition opacity 0.5s ease-in-out !important 15 | transition opacity 0.5s ease-in-out !important 16 | opacity 0 !important 17 | 18 | .d-positive 19 | color $positive-color 20 | 21 | .d-negative 22 | color $negative-color 23 | 24 | .d-highlight 25 | color $highlight-color -------------------------------------------------------------------------------- /ember/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | 10 | this.route('login'); 11 | this.route('register'); 12 | this.route('settings'); 13 | this.route('plans'); 14 | this.route('importer'); 15 | this.route('about'); 16 | this.route('password-reset'); 17 | 18 | this.resource('entries', function() { 19 | this.route('new'); 20 | this.route('edit', {path: '/:entry_id/edit'}); 21 | this.route('show', {path: '/:entry_id'}); 22 | }); 23 | 24 | }); 25 | 26 | export default Router; 27 | -------------------------------------------------------------------------------- /ember/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /ember/app/templates/entries/_show.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{pretty-time entryDate}} 5 |
6 |
7 |
{{pretty-day entryDate}}
8 |
9 |
10 | 11 |
12 | {{#link-to "entries.edit" id class="btn btn-default" title='Edit Entry'}} 13 | 14 | {{/link-to}} 15 | 18 |
19 | 20 | {{showdown-html body}} 21 |
-------------------------------------------------------------------------------- /ember/app/templates/entries/_list_item.hbs: -------------------------------------------------------------------------------- 1 |
2 |
{{pretty-time entryDate}}
3 |
{{pretty-day entryDate}}
4 |
5 | {{!-- Parent controller is entries.index --}} 6 | {{#if parentController.search.length}} 7 | {{showdown-with-search body parentController.search}} 8 | {{else}} 9 | {{showdown-html body}} 10 | {{/if}} 11 |
12 | 13 |
14 | {{#link-to "entries.edit" id class="btn btn-default"}}{{/link-to}} 15 | 16 |
17 |
-------------------------------------------------------------------------------- /ember/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diary", 3 | "dependencies": { 4 | "handlebars": "~1.3.0", 5 | "jquery": "^1.11.1", 6 | "ember": "1.8.1", 7 | "ember-data": "1.0.0-beta.12", 8 | "ember-resolver": "~0.1.7", 9 | "loader.js": "stefanpenner/loader.js#1.0.1", 10 | "ember-cli-shims": "stefanpenner/ember-cli-shims#0.0.3", 11 | "ember-cli-test-loader": "rwjblue/ember-cli-test-loader#0.0.4", 12 | "ember-load-initializers": "stefanpenner/ember-load-initializers#0.0.2", 13 | "ember-qunit": "0.1.8", 14 | "ember-qunit-notifications": "0.0.4", 15 | "qunit": "~1.15.0", 16 | "ember-simple-auth": "0.7.1", 17 | "jquery-autosize": "~1.18.12", 18 | "moment-timezone": "~0.2.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ember/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Controller.extend({ 4 | needs: "register", 5 | working: Ember.computed.alias('controllers.register.working'), 6 | errors: Ember.computed.alias('controllers.register.errors'), 7 | email: Ember.computed.alias('controllers.register.email'), 8 | password: Ember.computed.alias('controllers.register.password'), 9 | actions: { 10 | register: function() { 11 | this.get('controllers.register').registerUser(); 12 | }, 13 | focusRegister: function() { 14 | var target = $('.d-auth-form input:first'); 15 | target.focus(); 16 | $('html, body').animate({ 17 | scrollTop: 0 18 | }, 500); 19 | } 20 | } 21 | }); -------------------------------------------------------------------------------- /rails/app/controllers/plans_controller.rb: -------------------------------------------------------------------------------- 1 | class PlansController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def update_plan 5 | @user = current_user 6 | @user.stripe_token = params[:token] 7 | 8 | if @user.update_plan(params[:plan]) && @user.save 9 | render json: @user, status: 200 10 | else 11 | render json: { errors: @user.errors.full_messages }, status: :unprocessable_entity 12 | end 13 | end 14 | 15 | def cancel_plan 16 | @user = current_user 17 | if @user.cancel_plan && @user.save 18 | render json: @user, status: 200 19 | else 20 | render json: { errors: @user.errors.full_messages }, status: :unprocessable_entity 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /rails/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /rails/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /rails/app/views/entry_mailer/welcome.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome to DayJot, the simple, permanent journaling app.

2 | 3 |

How's your day going? To get started, simply reply to this email with your first entry.

4 | 5 |

A few notes:

6 | 7 | 12 | 13 |

If you have any questions, please don't hesitate to shoot an email to help@dayjot.com.

-------------------------------------------------------------------------------- /rails/db/migrate/20141109001231_add_base_user_fields.rb: -------------------------------------------------------------------------------- 1 | class AddBaseUserFields < ActiveRecord::Migration 2 | def change 3 | add_column :users, :email_times, :hstore 4 | add_column :users, :email_key, :string 5 | 6 | add_column :users, :last_export_time, :datetime 7 | 8 | add_column :users, :plan, :string 9 | add_column :users, :plan_started, :datetime 10 | add_column :users, :plan_canceled, :datetime 11 | add_column :users, :plan_status, :string, default: 'needs_card' 12 | add_column :users, :stripe_customer_id, :string 13 | add_column :users, :last_4_digits, :string 14 | 15 | add_column :users, :time_zone, :string, default: 'US/Pacific' 16 | add_column :users, :status, :string, default: 'active' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /ember/app/templates/passwords/reset-start-form.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#each errors}} 3 |
4 | {{this}} 5 |
6 | {{/each}} 7 | 8 | {{#if message}} 9 | 10 | {{/if}} 11 | 12 | {{input placeholder='Email' type='email' value=email autocomplete='off' autocapitalize="none"}} 13 | 24 |
-------------------------------------------------------------------------------- /ember/app/templates/users/register-form.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{#each errors}} 4 |
5 | {{this}} 6 |
7 | {{/each}} 8 | 9 | {{input placeholder='Email' type='email' value=email autocomplete='off' autocapitalize="none"}} 10 | {{input placeholder='Password' type='password' value=password autocomplete='off'}} 11 | 18 | 19 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /rails/app/views/entry_mailer/daily.html.erb: -------------------------------------------------------------------------------- 1 |

Just reply to this email with your entry for <%= Time.now.in_time_zone(@user.time_zone).strftime("%B %-d") %>.

2 | 3 |

---

4 | 5 | <% if @user.include_email_memory %> 6 | <% if @show_entry %> 7 |

<%= @entry.time_ago(@user) %> ago you wrote...

8 | 9 | <%= markdown(@entry.body) %> 10 | 11 |

---

12 | 13 |

View Entry: <%= entry_url(@entry.entry_date) %>

14 | 15 | <% else %> 16 |

Keep writing...once you have a few entries you'll see a blast from the past show up here!

17 | <% end %> 18 | <% end %> 19 | 20 |

Past entries: https://dayjot.com/entries

21 | 22 |

Unsubscribe: https://dayjot.com/settings

-------------------------------------------------------------------------------- /ember/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise", 6 | "moment", 7 | "_", 8 | "StripeCheckout", 9 | "Pikaday", 10 | "$", 11 | "Showdown" 12 | ], 13 | "browser" : true, 14 | "boss" : true, 15 | "curly": true, 16 | "debug": false, 17 | "devel": true, 18 | "eqeqeq": true, 19 | "evil": true, 20 | "forin": false, 21 | "immed": false, 22 | "laxbreak": false, 23 | "newcap": true, 24 | "noarg": true, 25 | "noempty": false, 26 | "nonew": false, 27 | "nomen": false, 28 | "onevar": false, 29 | "plusplus": false, 30 | "regexp": false, 31 | "undef": true, 32 | "sub": true, 33 | "strict": false, 34 | "white": false, 35 | "eqnull": true, 36 | "esnext": true, 37 | "unused": true 38 | } 39 | -------------------------------------------------------------------------------- /ember/app/templates/users/login-form.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{#if error}} 4 |
{{error}}
5 | {{/if}} 6 | 7 | {{input id='identification' placeholder='Email' value=identification autocomplete='off' autocapitalize="none"}} 8 | {{input id='password' placeholder='Password' type='password' value=password autocomplete='off'}} 9 | 10 | 17 | 18 | 22 |
-------------------------------------------------------------------------------- /rails/app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ActionMailer::Base 2 | default "Message-ID" => lambda {|_| "<#{SecureRandom.uuid}@dayjot.com>"} 3 | default from: "help@dayjot.com" 4 | 5 | def export_entries(user_id) 6 | @user = User.find(user_id) 7 | return unless @user 8 | @user.last_export_time = Time.now 9 | @user.save 10 | 11 | export_text = "" 12 | @user.entries.order("entry_date DESC").each do |e| 13 | export_text += "#{e.entry_date.strftime("%Y-%m-%d")}\n" 14 | export_text += e.body 15 | export_text += "\n\n" 16 | end 17 | 18 | filename = "dayjot_entries_#{Time.now.to_i}.txt" 19 | attachments[filename] = {:mime_type => 'text/plain', :content => export_text} 20 | 21 | mail to: @user.email, subject: "Your DayJot Entry Export" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /ember/app/controllers/login.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Controller.extend({ 4 | authenticator: 'simple-auth-authenticator:devise', 5 | 6 | identification: null, 7 | password: null, 8 | error: null, 9 | working: false, 10 | 11 | actions: { 12 | authenticate: function() { 13 | var _this = this, 14 | data = this.getProperties('identification', 'password'); 15 | 16 | this.setProperties({ 17 | working: true, 18 | password: null, 19 | error: null 20 | }); 21 | 22 | this.get('session').authenticate('simple-auth-authenticator:devise', data).then(function() { 23 | // authentication was successful 24 | }, function(data) { 25 | _this.set('working', false); 26 | _this.set('error', data.error); 27 | }); 28 | } 29 | } 30 | }); -------------------------------------------------------------------------------- /ember/app/views/site-nav.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.View.extend({ 4 | templateName: 'site-nav', 5 | tagName: 'nav', 6 | classNames: ['navbar','navbar-default'], 7 | 8 | todayDate: moment().format('YYYY-MM-DD'), 9 | searchTerm: "", 10 | 11 | actions: { 12 | search: function() { 13 | this.get('controller').send('search', this.get('searchTerm')); 14 | }, 15 | toggleDropdown: function() { 16 | var target = this.$('.dropdown-toggle'), 17 | parent = target.parents('li:first'); 18 | 19 | parent.toggleClass('open'); 20 | } 21 | }, 22 | 23 | didInsertElement: function() { 24 | $('body').on('click', function(e) { 25 | if (!$(e.target).hasClass('dropdown-toggle')) { 26 | $('.dropdown-toggle').parents('li:first').removeClass('open'); 27 | } 28 | }); 29 | } 30 | 31 | }); -------------------------------------------------------------------------------- /ember/app/templates/passwords/reset-finish-form.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#each errors}} 3 |
4 | {{this}} 5 |
6 | {{/each}} 7 | 8 | {{#if message}} 9 | 10 | {{/if}} 11 | 12 | {{input placeholder='New Password' type='password' value=newPassword1 autocomplete='off' autocapitalize="none"}} 13 | {{input placeholder='New Password Again' type='password' value=newPassword2 autocomplete='off' autocapitalize="none"}} 14 | 15 | 26 |
-------------------------------------------------------------------------------- /ember/app/templates/home-nav.hbs: -------------------------------------------------------------------------------- 1 |
2 | 24 |
-------------------------------------------------------------------------------- /ember/app/routes/entries/index.js: -------------------------------------------------------------------------------- 1 | import PlanRequiredRoute from "dayjot/routes/plan-required"; 2 | 3 | export default PlanRequiredRoute.extend({ 4 | 5 | queryParams: { 6 | search: { 7 | refreshModel: true 8 | }, 9 | when: { 10 | refreshModel: true 11 | } 12 | }, 13 | 14 | search: "", 15 | when: "", 16 | 17 | model: function(params, data) { 18 | return this.store.find('entry', data.queryParams); 19 | }, 20 | 21 | setupController: function(controller, model) { 22 | this._super(controller, model); 23 | controller.reset(); 24 | }, 25 | 26 | actions: { 27 | loadMore: function() { 28 | var controller = this.get('controller'), 29 | nextPage = controller.get('page') + 1, 30 | search = controller.get('search'), 31 | when = controller.get('when'); 32 | 33 | this.store.find('entry', {search: search, when: when, page: nextPage}).then(function(entries) { 34 | controller.loadedMore(entries, nextPage); 35 | }); 36 | } 37 | } 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /rails/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 893607fc20b9699afbb4ae26d9a798b7b247ec121b7e81f213f175cb4619936f48c8c61a850ef7bbb3804c8f9bf78b0fc5e0593e5f00fa5cfd7abe9a8682edba 15 | 16 | test: 17 | secret_key_base: 47d03bf6de37b99e5358362a28b37471b4369728237349658a2de6cbb659e48dcfe7ba154bb5f40be0229d1b318c2f3482e79ebdd47380483d7d314cd90ab46e 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /ember/Brocfile.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | 3 | var EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | var app = new EmberApp(); 6 | 7 | // Use `app.import` to add additional libraries to the generated 8 | // output files. 9 | // 10 | // If you need to use different assets in different 11 | // environments, specify an object as the first parameter. That 12 | // object's keys should be the environment name and the values 13 | // should be the asset to use in that environment. 14 | // 15 | // If the library that you are including contains AMD or ES6 16 | // modules that you would like to import into your application 17 | // please specify an object with the list of modules as keys 18 | // along with the exports of each module as its value. 19 | 20 | app.import('vendor/js/bootstrap.js'); 21 | app.import('vendor/js/underscore.js'); 22 | app.import('vendor/js/pikaday.js'); 23 | app.import('vendor/js/pikaday.jquery.js'); 24 | app.import('vendor/js/jquery-ui.js'); 25 | app.import('bower_components/jquery-autosize/jquery.autosize.js'); 26 | app.import('bower_components/moment-timezone/builds/moment-timezone-with-data-2010-2020.min.js'); 27 | 28 | module.exports = app.toTree(); 29 | -------------------------------------------------------------------------------- /rails/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby "2.1.4" 4 | 5 | gem 'rails', '4.1.7' 6 | 7 | # DB 8 | gem 'pg' 9 | gem 'pg_search' 10 | gem 'randomized_field' 11 | 12 | # authentication 13 | gem 'devise' 14 | 15 | # json api 16 | gem 'active_model_serializers' 17 | 18 | # Cors support 19 | gem 'rack-cors', require: 'rack/cors' 20 | 21 | # pagination 22 | gem 'kaminari' 23 | 24 | # payments 25 | gem 'stripe' 26 | 27 | # emails 28 | gem 'mailgun' 29 | 30 | # background processing 31 | gem 'delayed_job_active_record' 32 | gem 'delayed_job_web' 33 | 34 | # heroku process management 35 | gem "hirefire-resource" 36 | 37 | # markdown 38 | gem 'redcarpet' 39 | 40 | # date parsing 41 | gem 'chronic' 42 | 43 | group :production do 44 | gem 'rails_12factor' 45 | gem 'puma' 46 | gem 'newrelic_rpm' 47 | end 48 | 49 | group :development, :test do 50 | gem 'rspec-rails', '~> 3.0' 51 | gem 'shoulda' 52 | end 53 | 54 | group :development do 55 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 56 | gem 'spring' 57 | gem 'figaro' 58 | gem 'quiet_assets' 59 | gem 'foreman' 60 | gem 'byebug' 61 | end 62 | -------------------------------------------------------------------------------- /rails/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | # ENTRIES 4 | resources :entries do 5 | post 'handle_email' => 'entries#handle_email', on: :collection 6 | post 'export' => 'entries#export', on: :collection 7 | delete '' => 'entries#destroy_all', on: :collection 8 | end 9 | 10 | # PLANS 11 | post 'update_plan' => 'plans#update_plan', as: :update_plan 12 | post 'update_card' => 'plans#update_card', as: :update_card 13 | post 'cancel_plan' => 'plans#cancel_plan', as: :cancel_plan 14 | 15 | # PASSWORDS 16 | post 'start_password_reset' => 'users#start_password_reset' 17 | put 'finish_password_reset' => 'users#finish_password_reset' 18 | get 'password-reset' => 'application#index', as: :edit_user_password 19 | 20 | # USERS 21 | devise_for :users, controllers: { sessions: 'sessions' }, :skip => [:passwords] 22 | resources :users, only: [:create, :update] do 23 | get 'me' => 'users#me', on: :collection 24 | end 25 | 26 | # background processing admin 27 | match "/delayed_job" => DelayedJobWeb, :anchor => false, via: [:get, :post] 28 | 29 | # catch-all for ember app 30 | get '*path' => 'application#index', :constraints => { :format => 'html' } 31 | 32 | end 33 | -------------------------------------------------------------------------------- /rails/app/mailers/entry_mailer.rb: -------------------------------------------------------------------------------- 1 | class EntryMailer < ActionMailer::Base 2 | helper :entries 3 | default "Message-ID" => lambda {|_| "<#{SecureRandom.uuid}@post.dayjot.com>"} 4 | 5 | def welcome(user_id) 6 | @user = User.find(user_id) 7 | return unless @user 8 | 9 | mail to: @user.email, from: "DayJot <#{@user.email_key}@post.dayjot.com>", subject: "Your first DayJot entry" 10 | 11 | @user.update_column(:last_reminder_sent_at, DateTime.now.utc) 12 | end 13 | 14 | def daily(user_id) 15 | @user = User.find(user_id) 16 | # Don't email if we can't find the user, or they've already been sent an email today. 17 | return if !@user || @user.daily_email_sent_today? 18 | 19 | @entry = @user.random_entry(Time.now.in_time_zone(@user.time_zone).strftime("%Y-%m-%d")) 20 | @show_entry = @user.entries.count > 5 && @entry ? true : false 21 | 22 | mail to: @user.email, 23 | from: "DayJot <#{@user.email_key}@post.dayjot.com>", 24 | subject: "It's #{Time.now.in_time_zone(@user.time_zone).strftime("%A, %b %-d")} - How did your day go?" 25 | 26 | @user.update_column(:last_reminder_sent_at, DateTime.now.utc) 27 | @user.increment!(:reminder_emails_sent) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /ember/app/views/textarea-autosize.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.TextArea.extend({ 4 | typing: false, 5 | fade: false, 6 | 7 | didInsertElement: function() { 8 | this.$().autosize(); 9 | var _this = this; 10 | $(document).on('mousemove tap click', function() { 11 | if (_this.get('typing')) { 12 | _this.set('typing', false); 13 | if(_this.get('fade')) { 14 | $('.d-when,.btn,.navbar').removeClass('fadeout'); 15 | } 16 | } 17 | }); 18 | this.$().on('blur', function() { 19 | if (_this.get('typing')) { 20 | _this.set('typing', false); 21 | if(_this.get('fade')) { 22 | $('.d-when,.btn,.navbar').removeClass('fadeout'); 23 | } 24 | } 25 | }); 26 | }, 27 | 28 | willDestroyElement: function(){ 29 | this.$().trigger('autosize.destroy'); 30 | $(document).unbind("mousemove tap click"); 31 | this.$().unbind('blur'); 32 | }, 33 | 34 | keyPress: function() { 35 | if (!this.get('typing')) { 36 | this.set('typing', true); 37 | $('.d-error-box').remove(); 38 | if(this.get('fade')) { 39 | $('.d-when,.btn,.navbar').addClass('fadeout'); 40 | } 41 | } 42 | } 43 | }); -------------------------------------------------------------------------------- /ember/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dayjot", 3 | "version": "0.0.0", 4 | "private": true, 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "ember server", 11 | "build": "ember build", 12 | "test": "ember test" 13 | }, 14 | "repository": "https://github.com/stefanpenner/ember-cli", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "^2.0.0", 22 | "broccoli-ember-hbs-template-compiler": "^1.6.1", 23 | "broccoli-stylus-single": "^0.3.0", 24 | "ember-cli": "0.1.4", 25 | "ember-cli-dependency-checker": "0.0.6", 26 | "ember-cli-esnext": "0.1.1", 27 | "ember-cli-ic-ajax": "0.1.1", 28 | "ember-cli-inject-live-reload": "^1.3.0", 29 | "ember-cli-qunit": "0.1.2", 30 | "ember-cli-moment": "0.0.1", 31 | "ember-cli-showdown": "^0.1.3", 32 | "ember-cli-simple-auth": "^0.7.1", 33 | "ember-cli-simple-auth-devise": "^0.7.1", 34 | "ember-data": "^1.0.0-beta.12", 35 | "ember-export-application-global": "^1.0.0", 36 | "ember-notify": "^1.2.0", 37 | "express": "^4.8.5", 38 | "glob": "^4.0.5", 39 | "stylus": "^0.49.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ember/app/templates/entries/show.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{#if prevEntry}} 5 | {{#link-to "entries.show" prevEntry class="btn btn-default" title='Previous Entry'}} 6 | older 7 | {{/link-to}} 8 | {{else}} 9 |
older
10 | {{/if}} 11 | 12 | {{#if randomEntry}} 13 | {{#link-to "entries.show" randomEntry class="btn btn-default" title='Random Entry'}} 14 | random 15 | {{/link-to}} 16 | {{else}} 17 |
random
18 | {{/if}} 19 | 20 | {{#if nextEntry}} 21 | {{#link-to "entries.show" nextEntry class="btn btn-default" title='Next Entry'}} 22 | newer 23 | {{/link-to}} 24 | {{else}} 25 |
newer
26 | {{/if}} 27 |
28 |
29 | 30 | {{#if id}} 31 | {{partial "entries/show"}} 32 | {{else}} 33 | {{view "entries/form"}} 34 | {{/if}} 35 |
-------------------------------------------------------------------------------- /ember/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DayJot. An alternative to OhLife. 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{content-for 'head'}} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{content-for 'head-footer'}} 22 | 23 | 24 | {{content-for 'body'}} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{content-for 'body-footer'}} 33 | 34 | 35 | -------------------------------------------------------------------------------- /rails/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | # protect_from_forgery with: :null_session 5 | 6 | before_action :authenticate_user_from_token!, :handle_html 7 | around_action :user_time_zone, if: :current_user 8 | 9 | def index 10 | render file: 'public/index.html' 11 | end 12 | 13 | protected 14 | 15 | def authenticate_user! 16 | render(json: {}, status: 401) unless current_user 17 | end 18 | 19 | private 20 | 21 | def authenticate_user_from_token! 22 | authenticate_with_http_token do |token, options| 23 | user_email = options[:user_email].presence 24 | user = user_email && User.find_by_email(user_email) 25 | 26 | if user && Devise.secure_compare(user.authentication_token, token) 27 | request.env['devise.skip_trackable'] = true 28 | sign_in user, store: false 29 | end 30 | end 31 | end 32 | 33 | def user_time_zone(&block) 34 | Time.use_zone(current_user.time_zone, &block) 35 | end 36 | 37 | # If this is a get request for HTML, just render the ember app. 38 | def handle_html 39 | render 'public/index.html' if request.method == 'GET' && request.headers['Accept'].match(/html/) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ember/app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import DS from "ember-data"; 3 | import ENV from 'dayjot/config/environment'; 4 | 5 | export default DS.RESTAdapter.extend({ 6 | host: ENV.APP.API_HOST, 7 | 8 | /** 9 | The ActiveModelAdapter overrides the `ajaxError` method 10 | to return a DS.InvalidError for all 422 Unprocessable Entity 11 | responses. 12 | 13 | A 422 HTTP response from the server generally implies that the request 14 | was well formed but the API was unable to process it because the 15 | content was not semantically correct or meaningful per the API. 16 | 17 | For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918 18 | https://tools.ietf.org/html/rfc4918#section-11.2 19 | 20 | @method ajaxError 21 | @param jqXHR 22 | @return error 23 | */ 24 | ajaxError: function(jqXHR) { 25 | var error = this._super(jqXHR); 26 | 27 | if (jqXHR && jqXHR.status === 422) { 28 | var response = Ember.$.parseJSON(jqXHR.responseText), 29 | errors = {}; 30 | 31 | if (response.errors !== undefined) { 32 | errors = response.errors; 33 | } 34 | return new DS.InvalidError(errors); 35 | } else if (jqXHR && jqXHR.status === 401) { 36 | return new DS.InvalidError({}); 37 | } else { 38 | return error; 39 | } 40 | } 41 | }); -------------------------------------------------------------------------------- /ember/app/views/entries/list.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.View.extend({ 4 | templateName: 'entries/list', 5 | 6 | actions: { 7 | loadMore: function() { 8 | this.get('controller').loadMore(); 9 | } 10 | }, 11 | 12 | // this is called every time we scroll 13 | didScroll: function(){ 14 | if (this.isScrolledToBottom()) { 15 | this.get('controller').loadMore(); 16 | } 17 | }, 18 | 19 | // we check if we are at the bottom of the page 20 | isScrolledToBottom: function(){ 21 | var distanceToViewportTop = ( 22 | $(document).height() - $(window).height()); 23 | var viewPortTop = $(document).scrollTop(); 24 | 25 | if (viewPortTop === 0) { 26 | // if we are at the top of the page, don't do 27 | // the infinite scroll thing 28 | return false; 29 | } 30 | 31 | return (viewPortTop - distanceToViewportTop === 0); 32 | }, 33 | 34 | didInsertElement: function(){ 35 | // we want to make sure 'this' inside `didScroll` refers 36 | // to the IndexView, so we use jquery's `proxy` method to bind it 37 | $(window).on('scroll', $.proxy(this.didScroll, this)); 38 | }, 39 | willDestroyElement: function(){ 40 | // have to use the same argument to `off` that we did to `on` 41 | $(window).off('scroll', $.proxy(this.didScroll, this)); 42 | } 43 | }); -------------------------------------------------------------------------------- /rails/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module DayJotApi 10 | class Application < Rails::Application 11 | 12 | config.middleware.insert_before "ActionDispatch::Static", "Rack::Cors" do 13 | allow do 14 | origins '*' 15 | resource '*', :headers => :any, :methods => [:get, :post, :options, :put, :delete] 16 | end 17 | end 18 | 19 | config.autoload_paths += %W(#{config.root}/lib) 20 | 21 | # Settings in config/environments/* take precedence over those specified here. 22 | # Application configuration should go into files in config/initializers 23 | # -- all .rb files in that directory are automatically loaded. 24 | 25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 27 | # config.time_zone = 'Central Time (US & Canada)' 28 | 29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 31 | # config.i18n.default_locale = :de 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /ember/README.md: -------------------------------------------------------------------------------- 1 | # Diary 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | 5 | A short introduction of this app could easily go here. 6 | 7 | ## Prerequisites 8 | 9 | You will need the following things properly installed on your computer. 10 | 11 | * [Git](http://git-scm.com/) 12 | * [Node.js](http://nodejs.org/) (with NPM) and [Bower](http://bower.io/) 13 | 14 | ## Installation 15 | 16 | * `git clone ` this repository 17 | * change into the new directory 18 | * `npm install` 19 | * `bower install` 20 | 21 | ## Running / Development 22 | 23 | * `ember server` 24 | * Visit your app at http://localhost:4200. 25 | 26 | ### Code Generators 27 | 28 | Make use of the many generators for code, try `ember help generate` for more details 29 | 30 | ### Running Tests 31 | 32 | * `ember test` 33 | * `ember test --server` 34 | 35 | ### Building 36 | 37 | * `ember build` (development) 38 | * `ember build --environment production` (production) 39 | 40 | ### Deploying 41 | 42 | Specify what it takes to deploy your app. 43 | 44 | ## Further Reading / Useful Links 45 | 46 | * ember: http://emberjs.com/ 47 | * ember-cli: http://www.ember-cli.com/ 48 | * Development Browser Extensions 49 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 50 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 51 | 52 | -------------------------------------------------------------------------------- /rails/db/migrate/20141122185234_create_delayed_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateDelayedJobs < ActiveRecord::Migration 2 | def self.up 3 | create_table :delayed_jobs, :force => true do |table| 4 | table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue 5 | table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually. 6 | table.text :handler, :null => false # YAML-encoded string of the object that will do work 7 | table.text :last_error # reason for last failure (See Note below) 8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. 9 | table.datetime :locked_at # Set when a client is working on this object 10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) 11 | table.string :locked_by # Who is working on this object (if locked) 12 | table.string :queue # The name of the queue this job is in 13 | table.timestamps 14 | end 15 | 16 | add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' 17 | end 18 | 19 | def self.down 20 | drop_table :delayed_jobs 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /ember/app/components/calendar-date-picker.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Component.extend({ 4 | classNames: ['calendar-date-picker'], 5 | _picker: null, 6 | defaultDate: new Date(), 7 | attributeBindings: ['readonly'], 8 | readonly: true, 9 | value: "", 10 | 11 | actions: { 12 | togglePicker: function() { 13 | var picker = this.get('_picker'); 14 | if (picker.isVisible()) { 15 | picker.hide(); 16 | } else { 17 | picker.show(); 18 | } 19 | } 20 | }, 21 | 22 | modelChangedValue: function(){ 23 | var picker = this.get("_picker"); 24 | if (picker){ 25 | picker.setDate(this.get("value")); 26 | } 27 | }.observes("value"), 28 | 29 | didInsertElement: function(){ 30 | var defaultDate = moment(this.get('defaultDate')); 31 | this.set('value', defaultDate.format('MMMM Do, YYYY')); 32 | var currentYear = (new Date()).getFullYear(); 33 | 34 | var picker = new Pikaday({ 35 | field: this.$('input').get(0), 36 | yearRange: [2005, currentYear], 37 | defaultDate: defaultDate.toDate(), 38 | setDefaultDate: defaultDate.toDate(), 39 | maxDate: new Date(), 40 | format: 'MMMM Do, YYYY' 41 | }); 42 | 43 | this.set("_picker", picker); 44 | }, 45 | 46 | willDestroyElement: function(){ 47 | var picker = this.get("_picker"); 48 | if (picker) { 49 | picker.destroy(); 50 | } 51 | this.set("_picker", null); 52 | } 53 | }); -------------------------------------------------------------------------------- /ember/app/templates/entries/form.hbs: -------------------------------------------------------------------------------- 1 | {{#if errors}} 2 |
3 | {{#each errors}} 4 |

5 | {{this}} 6 |

7 | {{/each}} 8 |
9 | {{/if}} 10 | 11 |
12 |
13 |
14 |
15 | {{calendar-date-picker value=entryDatePretty defaultDate=entryDate}} 16 |
17 |
18 | 19 | {{#if previewing}} 20 |
21 | {{showdown-html body}} 22 |
23 | {{else}} 24 | {{view 'textarea-autosize' value=body placeholder="Write here.." fade=true class='d-what' disabled=session.currentUser.mustSubscribe}} 25 | {{/if}} 26 |
27 | 28 |
29 |
30 | 37 | 44 |
45 |
46 |
-------------------------------------------------------------------------------- /ember/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dayjot Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 32 | 33 | {{content-for 'head-footer'}} 34 | {{content-for 'test-head-footer'}} 35 | 36 | 37 | 38 | {{content-for 'body'}} 39 | {{content-for 'test-body'}} 40 | 41 | 42 | 43 | 44 | 45 | 46 | {{content-for 'body-footer'}} 47 | {{content-for 'test-body-footer'}} 48 | 49 | 50 | -------------------------------------------------------------------------------- /ember/app/routes/entries/show.js: -------------------------------------------------------------------------------- 1 | import PlanRequiredRoute from "../plan-required"; 2 | import Notify from 'ember-notify'; 3 | 4 | export default PlanRequiredRoute.extend({ 5 | model: function(params) { 6 | var _this = this; 7 | 8 | return this.store.find('entry', params.entry_id).then(function(entry) { 9 | // Force a reload if the meta data is out of date 10 | var meta = _this.store.metadataFor("entry"); 11 | if (meta.current_entry !== entry.get('entryDate')) { 12 | return entry.reload(); 13 | } else { 14 | return entry; 15 | } 16 | }, function(data) { 17 | if (data.status === 404) { 18 | // Set the meta data 19 | var meta = data.responseJSON.meta; 20 | _this.store.metaForType("entry", meta); 21 | 22 | // Build the dummy record, for use in the new form 23 | var entry = _this.store.createRecord('entry', { 24 | entryDate: params.entry_id 25 | }); 26 | 27 | return entry; 28 | } else { 29 | Notify.error(data.responseText, {closeAfter: 5000}); 30 | } 31 | }); 32 | }, 33 | setupController: function(controller, model) { 34 | this._super(controller, model); 35 | var meta = this.store.metadataFor("entry"); 36 | controller.setProperties({ 37 | nextEntry: meta.next_entry, 38 | randomEntry: meta.random_entry, 39 | prevEntry: meta.prev_entry, 40 | entryDatePretty: moment(model.get('entryDate')).format("MMMM Do, YYYY") 41 | }); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /ember/tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "QUnit", 10 | "define", 11 | "console", 12 | "equal", 13 | "notEqual", 14 | "notStrictEqual", 15 | "test", 16 | "asyncTest", 17 | "testBoth", 18 | "testWithDefault", 19 | "raises", 20 | "throws", 21 | "deepEqual", 22 | "start", 23 | "stop", 24 | "ok", 25 | "strictEqual", 26 | "module", 27 | "moduleFor", 28 | "moduleForComponent", 29 | "moduleForModel", 30 | "process", 31 | "expect", 32 | "visit", 33 | "exists", 34 | "fillIn", 35 | "click", 36 | "keyEvent", 37 | "triggerEvent", 38 | "find", 39 | "findWithAssert", 40 | "wait", 41 | "DS", 42 | "isolatedContainer", 43 | "startApp", 44 | "andThen", 45 | "currentURL", 46 | "currentPath", 47 | "currentRouteName" 48 | ], 49 | "node": false, 50 | "browser": false, 51 | "boss": true, 52 | "curly": false, 53 | "debug": false, 54 | "devel": false, 55 | "eqeqeq": true, 56 | "evil": true, 57 | "forin": false, 58 | "immed": false, 59 | "laxbreak": false, 60 | "newcap": true, 61 | "noarg": true, 62 | "noempty": false, 63 | "nonew": false, 64 | "nomen": false, 65 | "onevar": false, 66 | "plusplus": false, 67 | "regexp": false, 68 | "undef": true, 69 | "sub": true, 70 | "strict": false, 71 | "white": false, 72 | "eqnull": true, 73 | "esnext": true 74 | } 75 | -------------------------------------------------------------------------------- /rails/db/migrate/20141108212318_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table(:users, id: :uuid) do |t| 4 | ## Database authenticatable 5 | t.string :email, null: false, default: "" 6 | t.string :encrypted_password, null: false, default: "" 7 | 8 | ## Recoverable 9 | t.string :reset_password_token 10 | t.datetime :reset_password_sent_at 11 | 12 | ## Rememberable 13 | t.datetime :remember_created_at 14 | 15 | ## Trackable 16 | t.integer :sign_in_count, default: 0, null: false 17 | t.datetime :current_sign_in_at 18 | t.datetime :last_sign_in_at 19 | t.string :current_sign_in_ip 20 | t.string :last_sign_in_ip 21 | 22 | ## Confirmable 23 | # t.string :confirmation_token 24 | # t.datetime :confirmed_at 25 | # t.datetime :confirmation_sent_at 26 | # t.string :unconfirmed_email # Only if using reconfirmable 27 | 28 | ## Lockable 29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 30 | # t.string :unlock_token # Only if unlock strategy is :email or :both 31 | # t.datetime :locked_at 32 | 33 | 34 | t.timestamps 35 | end 36 | 37 | add_index :users, :email, unique: true 38 | add_index :users, :reset_password_token, unique: true 39 | # add_index :users, :confirmation_token, unique: true 40 | # add_index :users, :unlock_token, unique: true 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /ember/app/controllers/entries/index.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.ArrayController.extend({ 4 | itemController: 'entries.show', 5 | sortProperties: ['timestamp'], 6 | sortAscending: false, 7 | 8 | // Filtering 9 | queryParams: ['when','search'], 10 | when: "", 11 | search: "", 12 | 13 | // Pagination 14 | page: 1, 15 | perPage: 10, 16 | loadingMore: false, 17 | endOfList: false, 18 | 19 | lengthOrSearching: function() { 20 | return this.get('search').length || this.get('content').content.length > 0 ? true : false; 21 | }.property('content.length', 'search'), 22 | 23 | loadMore: function() { 24 | // don't load new data if we already are 25 | if (this.get('loadingMore') || this.get('endOfList')) { 26 | return; 27 | } 28 | 29 | this.set('loadingMore', true); 30 | 31 | // pass this action up the chain to the events hash on the route 32 | this.get('target').send('loadMore'); 33 | }, 34 | 35 | loadedMore: function(entries, page) { 36 | this.set('page', page); 37 | this.get('content').pushObjects(entries.content); 38 | this.set('loadingMore', false); 39 | if (entries.content.length < this.get('perPage')) { 40 | this.set('endOfList', true); 41 | } 42 | }, 43 | 44 | reset: function() { 45 | this.setProperties({ 46 | page: 1, 47 | loadingMore: false 48 | }); 49 | 50 | if (this.get('content').length < this.get('perPage')) { 51 | this.set('endOfList', true); 52 | } else { 53 | this.set('endOfList', false); 54 | } 55 | } 56 | }); -------------------------------------------------------------------------------- /ember/app/controllers/register.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.ObjectController.extend({ 4 | needs: 'settings', 5 | 6 | user: null, 7 | password: null, 8 | email: null, 9 | 10 | errors: null, 11 | working: false, 12 | 13 | actions: { 14 | register: function() { 15 | this.registerUser(); 16 | } 17 | }, 18 | 19 | registerUser: function() { 20 | var _this = this, 21 | data = { 22 | email: this.get('email'), 23 | password: this.get('password') 24 | }; 25 | 26 | // set the user's timezone 27 | var zones = this.get('controllers.settings.zones'), 28 | offset = moment().format('Z').split(':')[0], 29 | chosenZone = null; 30 | for (var i = 0; i < zones.length; i++) { 31 | if (zones[i].offset === offset) { 32 | chosenZone = zones[i]; 33 | break; 34 | } 35 | } 36 | if (chosenZone) { 37 | data.timeZone = chosenZone.value; 38 | } 39 | 40 | // Initialize the user 41 | if (!this.get('user')) { 42 | this.set('user', this.get('store').createRecord('user')); 43 | } 44 | this.get('user').setProperties(data); 45 | 46 | // Save the user 47 | this.set('working', true); 48 | this.get('user').save().then(function() { 49 | _this.set('user', null); 50 | _this.get('session').authenticate('simple-auth-authenticator:devise', {identification: data.email, password: data.password}); 51 | }, function(data) { 52 | _this.set('working', false); 53 | _this.set('errors', data.errors); 54 | }); 55 | } 56 | }); -------------------------------------------------------------------------------- /ember/app/views/users/email-time.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.View.extend({ 4 | tagName: 'div', 5 | classNames: ['form-group d-email-time'], 6 | templateName: 'users/email-time', 7 | 8 | day: null, 9 | emailTimes: null, 10 | 11 | label: function() { 12 | return "Don't email me on "+this.get("day").capitalize(); 13 | }.property('day'), 14 | 15 | times: function() { 16 | var times = [ 17 | {name: "5AM", value: 5}, 18 | {name: "6AM", value: 6}, 19 | {name: "7AM", value: 7}, 20 | {name: "8AM", value: 8}, 21 | {name: "9AM", value: 9}, 22 | {name: "10AM", value: 10}, 23 | {name: "11AM", value: 11}, 24 | {name: "12PM", value: 12}, 25 | {name: "1PM", value: 13}, 26 | {name: "2PM", value: 14}, 27 | {name: "3PM", value: 15}, 28 | {name: "4PM", value: 16}, 29 | {name: "5PM", value: 17}, 30 | {name: "6PM", value: 18}, 31 | {name: "7PM", value: 19}, 32 | {name: "8PM", value: 20}, 33 | {name: "9PM", value: 21}, 34 | {name: "10PM", value: 22}, 35 | {name: "11PM", value: 23} 36 | ]; 37 | return times; 38 | }.property(), 39 | 40 | selectionObserver: function() { 41 | var time = this.get('currentTime') ? parseInt(this.get('currentTime')) : null; 42 | this.set('controller.model.emailTimes.'+this.get('day'), time); 43 | }.observes('currentTime'), 44 | 45 | currentTime: function() { 46 | var time = this.get('emailTimes')[this.get('day')]; 47 | if (time) { 48 | time = parseInt(time); 49 | } 50 | return time; 51 | }.property('emailTimes') 52 | }); -------------------------------------------------------------------------------- /ember/vendor/js/pikaday.jquery.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Pikaday jQuery plugin. 3 | * 4 | * Copyright © 2013 David Bushell | BSD & MIT license | https://github.com/dbushell/Pikaday 5 | */ 6 | 7 | (function (root, factory) 8 | { 9 | 'use strict'; 10 | 11 | if (typeof exports === 'object') { 12 | // CommonJS module 13 | factory(require('jquery'), require('../pikaday')); 14 | } else if (typeof define === 'function' && define.amd) { 15 | // AMD. Register as an anonymous module. 16 | define(['jquery', 'pikaday'], factory); 17 | } else { 18 | // Browser globals 19 | factory(root.jQuery, root.Pikaday); 20 | } 21 | }(this, function ($, Pikaday) 22 | { 23 | 'use strict'; 24 | 25 | $.fn.pikaday = function() 26 | { 27 | var args = arguments; 28 | 29 | if (!args || !args.length) { 30 | args = [{ }]; 31 | } 32 | 33 | return this.each(function() 34 | { 35 | var self = $(this), 36 | plugin = self.data('pikaday'); 37 | 38 | if (!(plugin instanceof Pikaday)) { 39 | if (typeof args[0] === 'object') { 40 | var options = $.extend({}, args[0]); 41 | options.field = self[0]; 42 | self.data('pikaday', new Pikaday(options)); 43 | } 44 | } else { 45 | if (typeof args[0] === 'string' && typeof plugin[args[0]] === 'function') { 46 | plugin[args[0]].apply(plugin, Array.prototype.slice.call(args,1)); 47 | } 48 | } 49 | }); 50 | }; 51 | 52 | })); -------------------------------------------------------------------------------- /rails/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | 38 | config.action_mailer.default_url_options = { :host => 'http://localhost:3000' } 39 | end 40 | -------------------------------------------------------------------------------- /rails/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Set up Rails app. Run this script immediately after cloning the codebase. 4 | # https://github.com/thoughtbot/guides/tree/master/protocol 5 | 6 | # Exit if any subcommand fails 7 | set -e 8 | 9 | # Setup ember deps 10 | cd ../ember 11 | if ! command -v npm &>/dev/null; then 12 | echo "npm is not installed." 13 | echo "See http://bower.io for install instructions." 14 | fi 15 | echo "Installing npm dependencies.." 16 | npm install 17 | 18 | if ! command -v bower &>/dev/null; then 19 | echo "bower is not installed." 20 | echo "See https://www.npmjs.org for install instructions." 21 | fi 22 | echo "Installing bower dependencies.." 23 | bower install 24 | 25 | # Setup rails deps 26 | cd ../rails 27 | 28 | # Set up Ruby dependencies via Bundler 29 | echo "Installing ruby dependencies.." 30 | bundle install 31 | 32 | # Set up configurable environment variables 33 | if [ ! -f config/application.yml ]; then 34 | cp config/application.example.yml config/application.yml 35 | fi 36 | 37 | # Set up database and add any development seed data 38 | echo "Setting up the database.." 39 | bundle exec rake db:create 40 | bundle exec rake db:migrate 41 | 42 | # Add binstubs to PATH via export PATH=".git/safe/../../bin:$PATH" in ~/.zshenv 43 | mkdir -p .git/safe 44 | 45 | # Pick a port for Foreman 46 | echo "port: 3000" > .foreman 47 | 48 | # Print warning if Foreman is not installed 49 | if ! command -v foreman &>/dev/null; then 50 | echo "foreman is not installed." 51 | echo "See https://github.com/ddollar/foreman for install instructions." 52 | fi 53 | 54 | echo "Setup finished! Run 'foreman start -f Procfile.local' from the rails directory to start the server." -------------------------------------------------------------------------------- /rails/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | DayJot uses pgcrypto to encrypt the body text of entries. To set this up on your local machine, you must generate 4 | the PGP Keys. 5 | 6 | If you are on some Linux OS you probably have the command line tool called gpg that you can use already available. If you are on windows, you need to download them from somewhere like this page GNU Pg binaries. Way at the bottom of the page you should find gnupg-w32cli-1.4.10b.exe. Download and install that or you can simply extract the folder instead of installing and run from anywhere. By default it installs in folder C:\Program Files\GNU\GnuPG. You can copy these files anywhere. Really no need for installation. 7 | 8 | Next we do the following more or less verbatim from the PostgreSQL pgcrypto docs. These steps will work on Linux/Unix/Mac OSX or windows 9 | 10 | gpg --gen-key and follow the directions. Note if you don't need super security, you can just click enter to the password phrase thus one less argument you need to pass when decrypting your data. 11 | gpg --list-secret-keys This will provide you a list of keys one being the one you generated. It will look something like: 12 | sec 1024R/123ABCD 2010-06-15 13 | uid My data key (super data encrypt) 14 | ssb 1024R/999DEFG 2010-06-15 15 | 16 | Where the 1024R is the bit strength I chose and 123ABCD is the private key and 999DEFG is the public key. 17 | gpg -a --export 999DEFG > public.key Replacing the 999DEFG with hmm your public key code. This is the key you will need to encrypt data. 18 | gpg -a --export-secret-keys 123ABCD > secret.key again Replacing the 123ABCD with hmm yourprivate key code. This is the key you will need to decrypt data. 19 | 20 | Put your public key in .pgcrypto in the project root. 21 | 22 | Put your private key in secret.key in the project root. -------------------------------------------------------------------------------- /rails/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /ember/app/styles/media_queries.styl: -------------------------------------------------------------------------------- 1 | @media (max-width: 1024px) 2 | 3 | .d-nav 4 | position relative 5 | z-index 50 6 | background-color $body-bg-color 7 | background-image url($texture-url) 8 | transition none 9 | padding-left 0 10 | padding-right 0 11 | h1 12 | margin-left 1px 13 | h1 14 | .d-menu 15 | .d-menu-handle 16 | display inline-block 17 | i 18 | margin-right 2px 19 | .d-menu 20 | z-index 51 21 | float right 22 | margin-right 3px 23 | &:hover 24 | .d-menu-handle 25 | color white 26 | ul 27 | display block 28 | ul 29 | display none 30 | position absolute 31 | right -20px 32 | top -13px 33 | z-index -1 34 | background $purple 35 | padding 50px 20px 20px 50px 36 | border-radius 0 0 2px 2px 37 | box-shadow 0 0 1px rgba(0,0,0,0.6) 38 | text-align right 39 | a 40 | color white 41 | opacity 0.9 42 | &:hover 43 | color white 44 | opacity 1 45 | &:hover 46 | background inherit 47 | 48 | .d-sidebar 49 | padding-top 0 50 | 51 | .d-settings 52 | .d-about 53 | >h1 54 | padding-top 20px 55 | 56 | @media (max-width: 768px) 57 | .d-container 58 | .d-center-container 59 | padding-left 20px 60 | padding-right 20px 61 | 62 | .d-features 63 | li 64 | margin-bottom 15px 65 | 66 | .d-home 67 | .d-extra-features 68 | li 69 | margin-bottom 5px 70 | 71 | @media (max-width: 480px) 72 | .d-container 73 | .d-center-container 74 | padding-left 10px 75 | padding-right 10px 76 | .d-content-sidebar 77 | .d-sidebar 78 | padding 0 79 | width 100% 80 | .d-sidebar 81 | margin 20px 0 82 | width 100% 83 | .d-entry 84 | padding 30px 20px 20px 85 | -------------------------------------------------------------------------------- /ember/app/templates/entries/index.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#if lengthOrSearching}} 3 |
4 | {{view "entries/list"}} 5 |
6 | 7 | 38 | 39 | {{else}} 40 |
41 |
Ahh, a blank slate.
42 |
We've sent you your first entry email. Simply reply to it and your entry will appear here.
43 |
or
44 |
45 | {{#link-to "entries.new" class='btn btn-default'}}write your first entry{{/link-to}} 46 | {{#link-to "importer" class='btn btn-default'}}import previous entries{{/link-to}} 47 | {{#link-to "settings" class='btn btn-default'}}manage email settings{{/link-to}} 48 |
49 |
50 | {{/if}} -------------------------------------------------------------------------------- /ember/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dayjot', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | }, 20 | 21 | 'simple-auth': { 22 | crossOriginWhitelist: ['http://localhost:3000','http://localhost:4202','https://api.dayjot.com'], 23 | authorizer: 'simple-auth-authorizer:devise', 24 | authenticationRoute: 'index' 25 | } 26 | }; 27 | 28 | if (environment === 'development') { 29 | // ENV.APP.LOG_RESOLVER = true; 30 | ENV.APP.LOG_ACTIVE_GENERATION = true; 31 | // ENV.APP.LOG_TRANSITIONS = true; 32 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 33 | ENV.APP.LOG_VIEW_LOOKUPS = true; 34 | 35 | ENV.APP.API_HOST = "http://localhost:3000" 36 | // ENV.APP.API_HOST = "http://localhost:4202/development-gdsc3" 37 | 38 | ENV.APP.STRIPE_KEY = "pk_test_2undx7H0CeDLSDUX01k5bXfn"; 39 | } 40 | 41 | if (environment === 'test') { 42 | // Testem prefers this... 43 | ENV.baseURL = '/'; 44 | ENV.locationType = 'auto'; 45 | 46 | // keep test console output quieter 47 | ENV.APP.LOG_ACTIVE_GENERATION = false; 48 | ENV.APP.LOG_VIEW_LOOKUPS = false; 49 | 50 | ENV.APP.rootElement = '#ember-testing'; 51 | } 52 | 53 | if (environment === 'production') { 54 | // keep test console output quieter 55 | ENV.APP.LOG_ACTIVE_GENERATION = false; 56 | ENV.APP.LOG_VIEW_LOOKUPS = false; 57 | 58 | ENV.APP.API_HOST = "https://dayjot.com" 59 | ENV.APP.STRIPE_KEY = "pk_live_0pfal3NW90qouuEPy6LDuuUm"; 60 | } 61 | 62 | ENV['simple-auth-devise'] = { 63 | serverTokenEndpoint: ENV.APP.API_HOST+'/users/sign_in' 64 | } 65 | 66 | return ENV; 67 | }; 68 | -------------------------------------------------------------------------------- /ember/app/controllers/entries/show.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import Notify from 'ember-notify'; 3 | 4 | export default Ember.ObjectController.extend({ 5 | errors: null, 6 | saving: false, 7 | previewing: false, 8 | entryDatePretty: null, 9 | nextEntry: null, 10 | randomEntry: null, 11 | prevEntry: null, 12 | 13 | entryDidChange: function() { 14 | this.set('saving', false); 15 | this.set('errors', null); 16 | if (!this.get('model.entryDate')) { 17 | this.set('model.entryDate', new Date()); 18 | } 19 | }.observes('model'), 20 | 21 | dateDidChange: function() { 22 | this.set('model.entryDate', moment(this.get('entryDatePretty'), "MMMM Do, YYYY").utc().toDate()); 23 | }.observes('entryDatePretty'), 24 | 25 | actions: { 26 | remove: function() { 27 | this.removeEntry(); 28 | }, 29 | save: function() { 30 | this.saveEntry(); 31 | }, 32 | togglePreview: function() { 33 | this.set('previewing', !this.get('previewing')); 34 | } 35 | }, 36 | 37 | saveEntry: function() { 38 | this.set('errors', null); 39 | this.set('saving', true); 40 | var _this = this, 41 | user = this.get('session.currentUser'); 42 | 43 | this.get('model').save().then(function() { 44 | user.refresh(); 45 | _this.set('saving', false); 46 | _this.transitionToRoute('entries.show', _this.get('model.entryDate')); 47 | Notify.success('Entry saved.'); 48 | }, function(data) { 49 | _this.set('saving', false); 50 | _this.set('errors', data.errors); 51 | Notify.error(data.errors[0], {closeAfter: 5000}); 52 | }); 53 | }, 54 | 55 | removeEntry: function () { 56 | var r = confirm("Are you sure you want to erase this entry? This cannot be undone."); 57 | if (r === true) { 58 | var entry = this.get('model'), 59 | _this = this; 60 | 61 | entry.deleteRecord(); 62 | entry.save().then(function() { 63 | Notify.success('Entry erased.', {closeAfter: 5000}); 64 | var user = _this.get('session.currentUser'); 65 | user.refresh(); 66 | }); 67 | } 68 | } 69 | }); -------------------------------------------------------------------------------- /ember/app/styles/bootstrap_custom.styl: -------------------------------------------------------------------------------- 1 | .container 2 | max-width $site-width 3 | 4 | .btn 5 | .form-control 6 | border-radius 2px 7 | box-shadow none 8 | &:focus 9 | &.active 10 | &:active 11 | outline none 12 | 13 | .form-control 14 | &:focus 15 | box-shadow none 16 | 17 | .btn-primary 18 | background-color $action-color 19 | border-color $action-color 20 | border-bottom-color $action-color - 20% 21 | &:hover 22 | background-color $action-color - 10% 23 | border-color $action-color - 30% 24 | .btn-info 25 | background-color $info-color 26 | border-color $info-color 27 | border-bottom-color $info-color - 20% 28 | &:hover 29 | background-color $info-color - 10% 30 | border-color $info-color - 30% 31 | .btn-danger 32 | background-color $negative-color 33 | border-color $negative-color 34 | border-bottom-color $negative-color - 25% 35 | &:hover 36 | background-color $negative-color - 10% 37 | border-color $negative-color - 30% 38 | .btn-default 39 | border-color #CCC 40 | border-bottom-color #CCC - 20% 41 | &:hover 42 | border-color #CCC - 30% 43 | 44 | .navbar-default 45 | border none 46 | box-shadow 0 0 1px rgba(0,0,0,0.5) 47 | border-radius 0 48 | background-color #FCFCFC 49 | margin-bottom $base-padding 50 | .navbar-nav 51 | li 52 | i 53 | font-size 12px 54 | position relative 55 | top -1px 56 | margin-right 5px 57 | &:hover 58 | color $highlight-color + 40% 59 | a.active 60 | color $highlight-color 61 | text-shadow 0 0 1px rgba($highlight-color, 0.5) 62 | &:hover 63 | color $highlight-color 64 | cursor default 65 | .dropdown-toggle 66 | cursor pointer 67 | 68 | .badge 69 | border-radius 50% 70 | font-size 10px 71 | padding 4px 0 72 | text-align center 73 | width 20px 74 | background transparent 75 | color rgba($site-text-color, 0.5) 76 | border 1px solid rgba(0,0,0,0.2) 77 | 78 | .alert 79 | max-width $site-width 80 | margin 0 auto $base-padding auto 81 | .alert-centered 82 | text-align center 83 | 84 | .form-control[type="checkbox"] 85 | width auto 86 | height auto -------------------------------------------------------------------------------- /ember/app/templates/importer.hbs: -------------------------------------------------------------------------------- 1 | {{view "site-nav"}} 2 | 3 |
4 | 5 |
6 |
7 |
8 | Entry Importer 9 |
10 |
11 | 12 |

13 | If you have entries stored elsewhere, or have an export .txt file from OhLife, 14 | you can use this tool to add entries to DayJot in bulk. Simple paste 15 | the entry text below, and click "Start Import". Make sure to follow 16 | the formatting guide below. If you have any questions, email 17 | help@dayjot.com. 18 |

19 | 20 |
21 |

Format

22 | 23 |

24 | year-month-day 25 |
26 | Entry Text 27 |

28 | 2011-04-18 29 |
30 | Entry text 31 |

32 | 2011-04-19 33 |
34 | Entry Text 35 |

36 | 2011-04-20 37 |
38 | And so on.. 39 |

40 |
41 | 42 |
43 |

Paste Below

44 | {{view 'textarea-autosize' value=importText placeholder="Paste formatted entry text to import here." fade=false}} 45 | 46 |
47 |
48 |

> {{processingText}}

49 |

> Importing {{processingCount}} of {{entryCount}} entries.

50 | 51 | {{#if errors}} 52 | {{{errors}}} 53 | {{/if}} 54 | 55 | {{#if done}} 56 |

Done! Successfully added {{successCount}} entries.

57 | {{/if}} 58 |
59 |
60 | 61 |
62 | 63 | {{#if importText}} 64 | 65 | {{#if done}} 66 | 67 | {{/if}} 68 | {{/if}} 69 |
70 |
71 | 72 |
-------------------------------------------------------------------------------- /rails/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :authenticate_user!, except: [:create, :start_password_reset, :finish_password_reset] 3 | before_action :assert_reset_token_passed, only: [:finish_password_reset] 4 | 5 | def create 6 | user = User.new(register_params) 7 | 8 | if user.save 9 | render json: user, status: :created 10 | else 11 | render json: { errors: user.errors.full_messages }, status: :unprocessable_entity 12 | end 13 | end 14 | 15 | def update 16 | if current_user.update(user_params) 17 | render json: current_user 18 | else 19 | render json: { errors: current_user.errors.full_messages }, status: :unprocessable_entity 20 | end 21 | end 22 | 23 | def me 24 | if current_user 25 | render json: current_user, status: 200 26 | else 27 | render json: {}, status: 400 28 | end 29 | end 30 | 31 | def start_password_reset 32 | user = User.where(email: params[:email]).first 33 | user.send_reset_password_instructions if user 34 | render json: {} 35 | end 36 | 37 | def finish_password_reset 38 | user = User.with_reset_password_token(params[:reset_password_token]) 39 | if user 40 | if user.reset_password!(reset_params[:password], reset_params[:password_confirmation]) 41 | render json: {} 42 | else 43 | render json: { errors: user.errors.full_messages }, status: 400 44 | end 45 | else 46 | render json: { errors: ['Invalid password reset request.'] }, status: 403 47 | end 48 | end 49 | 50 | private 51 | 52 | def register_params 53 | params.require(:user).permit(:email, :password, :time_zone) 54 | end 55 | 56 | def user_params 57 | params.require(:user).permit(:include_email_memory, :time_zone, email_times: [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday]) 58 | end 59 | 60 | def reset_params 61 | params.permit(:password, :password_confirmation) 62 | end 63 | 64 | # Check if a reset_password_token is provided in the request 65 | def assert_reset_token_passed 66 | render(json: { errors: ['Invalid password reset request.'] }, status: 403) if params[:reset_password_token].blank? 67 | end 68 | 69 | end 70 | -------------------------------------------------------------------------------- /ember/app/controllers/password-reset.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import ENV from "dayjot/config/environment"; 3 | import Notify from 'ember-notify'; 4 | 5 | export default Ember.Controller.extend({ 6 | queryParams: ['reset_password_token'], 7 | reset_password_token: null, 8 | 9 | message: null, 10 | 11 | errors: null, 12 | working: false, 13 | done: false, 14 | 15 | email: null, 16 | newPassword1: null, 17 | newPassword2: null, 18 | 19 | actions: { 20 | start: function() { 21 | var _this = this; 22 | 23 | this.set('working', true); 24 | Ember.$.ajax({ 25 | url: ENV.APP.API_HOST + "/start_password_reset", 26 | type: "POST", 27 | data: {email: _this.get("email")}, 28 | dataType: 'json', 29 | success: function() { 30 | _this.set('errors', null); 31 | _this.set('working', false); 32 | _this.set('done', true); 33 | _this.set('message', 'Please check your email. If the email you specified exists in our system, we\'ve sent a password reset link to it.'); 34 | }, 35 | error: function(data) { 36 | _this.set('working', false); 37 | _this.set('errors', data); 38 | } 39 | }); 40 | }, 41 | finish: function() { 42 | var _this = this, 43 | data = { 44 | reset_password_token: this.get('reset_password_token'), 45 | password: this.get('newPassword1'), 46 | password_confirmation: this.get('newPassword2') 47 | }; 48 | 49 | this.set('working', true); 50 | Ember.$.ajax({ 51 | url: ENV.APP.API_HOST + "/finish_password_reset", 52 | type: "PUT", 53 | data: data, 54 | dataType: 'json', 55 | success: function() { 56 | _this.set('errors', null); 57 | _this.set('working', false); 58 | Notify.success("Password updated, please login.", {closeAfter: 5000}); 59 | _this.transitionToRoute('login'); 60 | }, 61 | error: function(data) { 62 | _this.set('working', false); 63 | _this.set('errors', data.responseJSON.errors); 64 | if (data.status === 403) { 65 | _this.transitionToRoute('password-reset', {queryParams: {reset_password_token: null}}); 66 | } 67 | } 68 | }); 69 | } 70 | } 71 | }); -------------------------------------------------------------------------------- /rails/lib/tasks/entry.rake: -------------------------------------------------------------------------------- 1 | namespace :entry do 2 | 3 | # rake entry:send_entries_test 4 | task :send_hourly_entries_test => :environment do 5 | user = User.where(:email=>"test@example.com").first 6 | user.send_entry_email 7 | end 8 | 9 | task :send_hourly_entries => :environment do 10 | users = User.active.in_good_standing 11 | users.each do |user| 12 | if user.send_entry_email_now? 13 | user.send_entry_email 14 | end 15 | end 16 | end 17 | 18 | # Tmp task to import entries from legacy DB 19 | task :import => :environment do 20 | # Remove all records 21 | User.delete_all 22 | Entry.delete_all 23 | 24 | file = File.read("#{Rails.root}/import/User.json") 25 | user_hash = JSON.parse(file) 26 | 27 | file = File.read("#{Rails.root}/import/Entry.json") 28 | entry_hash = JSON.parse(file) 29 | 30 | user_count = 0 31 | entry_count = 0 32 | 33 | user_hash["results"].each do |u| 34 | next unless u["email"] == "test@example.com" 35 | 36 | email_times = {} 37 | u["emailTimes"].each do |day,time| 38 | if time 39 | email_times[day] = Time.now.utc.change(hour: time.to_i).in_time_zone(u["timezone"]).hour 40 | end 41 | end 42 | 43 | user = User.create( 44 | email: u["email"], 45 | email_times: email_times, 46 | plan: u["plan"], 47 | plan_started: u["planStarted"] ? Chronic.parse(u["planStarted"]["iso"]) : nil, 48 | plan_status: u["planStatus"], 49 | status: u["status"], 50 | stripe_customer_id: u["stripeCustomerId"], 51 | time_zone: u["timezone"], 52 | password: SecureRandom.base64(20) 53 | ) 54 | user_count += 1 if user.persisted? 55 | 56 | entry_hash["results"].each do |e| 57 | next unless e["user"]["objectId"] == u["objectId"] 58 | 59 | entry = Entry.create( 60 | body: e["body"], 61 | created_at: Chronic.parse(e["createdAt"]), 62 | entry_date: e["entryDate"], 63 | source: e["source"], 64 | updated_at: Chronic.parse(e["updatedAt"]), 65 | user: user 66 | ) 67 | 68 | entry_count += 1 if entry.persisted? 69 | end 70 | end 71 | 72 | puts "Created #{user_count}/#{user_hash["results"].length} users and #{entry_count}/#{entry_hash["results"].length} entries." 73 | end 74 | 75 | end -------------------------------------------------------------------------------- /ember/app/routes/application.js: -------------------------------------------------------------------------------- 1 | // ember-simple-auth 2 | import Ember from "ember"; 3 | import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin'; 4 | import Notify from 'ember-notify'; 5 | import ENV from 'dayjot/config/environment'; 6 | 7 | export default Ember.Route.extend(ApplicationRouteMixin, { 8 | beforeModel: function(transition) { 9 | this._super(transition); 10 | return this.setCurrentUser(); 11 | }, 12 | actions: { 13 | sessionAuthenticationFailed: function(data) { 14 | this.controllerFor('login').set('working', false); 15 | this.controllerFor('login').set('loginErrorMessage', data.message); 16 | }, 17 | sessionInvalidationSucceeded: function() { 18 | this.transitionTo('index'); 19 | }, 20 | sessionAuthenticationSucceeded: function() { 21 | var _this = this; 22 | this.controllerFor('login').set('working', false); 23 | 24 | this.setCurrentUser().then(function() { 25 | if (_this.get('session.currentUser.mustSubscribe')) { 26 | _this.transitionTo('plans'); 27 | } else { 28 | _this.transitionTo('entries'); 29 | } 30 | }); 31 | }, 32 | authorizationFailed: function() { 33 | Notify.error("Could not be authenticated.. signing out.", {closeAfter: 5000}); 34 | this.get('session').invalidate(); 35 | }, 36 | search: function(term) { 37 | this.transitionTo('entries', {queryParams: {search: term}}); 38 | } 39 | }, 40 | 41 | setCurrentUser: function() { 42 | 43 | var _this = this, 44 | adapter = this.get('store').adapterFor('user'); 45 | 46 | if (this.get('session.isAuthenticated')) { 47 | return new Ember.RSVP.Promise(function(resolve) { 48 | adapter.ajax(ENV.APP.API_HOST + "/users/me", "GET", {}).then( 49 | function(response){ 50 | _this.store.pushPayload(response); 51 | var user = _this.store.find('user', response.user.id); 52 | resolve(user); 53 | }, 54 | function(response){ 55 | resolve(response); 56 | } 57 | ); 58 | }).then(function(user) { 59 | _this.set('session.currentUser', user); 60 | }, function() { 61 | Notify.error("Could not be authenticated.. signing out.", {closeAfter: 5000}); 62 | _this.get('session').invalidate(); 63 | }); 64 | } else { 65 | return new Ember.RSVP.Promise(function(resolve){ resolve(); }); 66 | } 67 | } 68 | }); -------------------------------------------------------------------------------- /rails/spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require 'spec_helper' 4 | require File.expand_path("../../config/environment", __FILE__) 5 | require 'rspec/rails' 6 | # Add additional requires below this line. Rails is not loaded until this point! 7 | 8 | # Requires supporting ruby files with custom matchers and macros, etc, in 9 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 10 | # run as spec files by default. This means that files in spec/support that end 11 | # in _spec.rb will both be required and run as specs, causing the specs to be 12 | # run twice. It is recommended that you do not name files matching this glob to 13 | # end with _spec.rb. You can configure this pattern with the --pattern 14 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 15 | # 16 | # The following line is provided for convenience purposes. It has the downside 17 | # of increasing the boot-up time by auto-requiring all files in the support 18 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 19 | # require only the support files necessary. 20 | # 21 | # Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } 22 | 23 | # Checks for pending migrations before tests are run. 24 | # If you are not using ActiveRecord, you can remove this line. 25 | ActiveRecord::Migration.maintain_test_schema! 26 | 27 | RSpec.configure do |config| 28 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 29 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 30 | 31 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 32 | # examples within a transaction, remove the following line or assign false 33 | # instead of true. 34 | config.use_transactional_fixtures = true 35 | 36 | # RSpec Rails can automatically mix in different behaviours to your tests 37 | # based on their file location, for example enabling you to call `get` and 38 | # `post` in specs under `spec/controllers`. 39 | # 40 | # You can disable this behaviour by removing the line below, and instead 41 | # explicitly tag your specs with their type, e.g.: 42 | # 43 | # RSpec.describe UsersController, :type => :controller do 44 | # # ... 45 | # end 46 | # 47 | # The different available types are documented in the features, such as in 48 | # https://relishapp.com/rspec/rspec-rails/docs 49 | config.infer_spec_type_from_file_location! 50 | end 51 | -------------------------------------------------------------------------------- /ember/app/models/user.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import DS from "ember-data"; 3 | import ENV from 'dayjot/config/environment'; 4 | 5 | export default DS.Model.extend({ 6 | lastUpdated: 0, 7 | 8 | email: DS.attr('string'), 9 | password: DS.attr('string'), 10 | passwordConfirmation: DS.attr('string'), 11 | encryptEntries: DS.attr('boolean'), 12 | 13 | status: DS.attr('string', {readOnly: true}), 14 | plan: DS.attr('string', {readOnly: true}), 15 | planStatus: DS.attr('string', {readOnly: true}), 16 | planStarted: DS.attr('date', {readOnly: true}), 17 | planCanceled: DS.attr('date', {readOnly: true}), 18 | trialEnd: DS.attr('date', {readOnly: true}), 19 | entryMonths: DS.attr('object', {readOnly: true}), 20 | 21 | timeZone: DS.attr('string'), 22 | emailTimes: DS.attr('object'), 23 | includeEmailMemory: DS.attr('boolean'), 24 | 25 | createdAt: DS.attr('date', {readOnly: true}), 26 | 27 | planActive: function() { 28 | if (this.get('status') === 'active' && (this.get('planStatus') === 'active' || this.get('trialActive'))) { 29 | return true; 30 | } else { 31 | return false; 32 | } 33 | }.property('status','planStatus'), 34 | 35 | planInactive: function() { 36 | return this.get('planActive') ? false : true; 37 | }.property('planActive'), 38 | 39 | trialActive: function() { 40 | return (this.get('trialEnd') >= new Date()) ? true : false; 41 | }.property('createdAt'), 42 | 43 | shouldSubscribe: function() { 44 | if (this.get('planStatus') !== 'active') { 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | }.property('status','planStatus'), 50 | 51 | mustSubscribe: function() { 52 | if (this.get('status') !== 'active' || (!this.get('trialActive') && this.get('planStatus') !== 'active')) { 53 | return true; 54 | } else { 55 | return false; 56 | } 57 | }.property('status','trialActive','planStatus'), 58 | 59 | planWasCanceled: function() { 60 | return this.get('planStatus') === 'canceled' ? true : false; 61 | }.property('planStatus'), 62 | 63 | // Refreshes the user session and updates the user 64 | // properties (called on page refresh in application route) 65 | refresh: function(){ 66 | var adapter = this.get('store').adapterFor(this), 67 | _this = this; 68 | return new Ember.RSVP.Promise(function(resolve) { 69 | adapter.ajax(ENV.APP.API_HOST + "/users/me", "GET", {}).then( 70 | function(response){ 71 | _this.store.pushPayload(response); 72 | _this.set('lastUpdated', new Date()); 73 | resolve(response); 74 | }, 75 | function(response){ 76 | resolve(); 77 | console.log('USER REFRESH FAILED: '); 78 | console.log(response); 79 | } 80 | ); 81 | }); 82 | } 83 | }); -------------------------------------------------------------------------------- /ember/app/templates/site-nav.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 14 | 15 | 16 | 59 |
-------------------------------------------------------------------------------- /ember/app/views/slider.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | var plans = [ 4 | { 5 | value: "$1", 6 | plan: "monthly_1", 7 | text: "a box of Tic Tacs.", 8 | index: 0 9 | }, 10 | { 11 | value: "$2", 12 | plan: "monthly_2", 13 | text: "an avacado.", 14 | index: 1 15 | }, 16 | { 17 | value: "$3", 18 | plan: "monthly_3", 19 | text: "a Tazo Chai Tea Latte.", 20 | index: 2 21 | }, 22 | { 23 | value: "$4", 24 | plan: "monthly_4", 25 | text: "a Double-Double animal style.", 26 | index: 3 27 | }, 28 | { 29 | value: "$5", 30 | plan: "monthly_5", 31 | text: "Sharknado on DVD.", 32 | index: 4 33 | }, 34 | { 35 | value: "$7", 36 | plan: "monthly_7", 37 | text: "a 4-pack of Dogfish Head 90 Minute IPA.", 38 | index: 5 39 | }, 40 | { 41 | value: "$10", 42 | plan: "monthly_10", 43 | text: "a night at the movies. By yourself.", 44 | index: 6 45 | }, 46 | { 47 | value: "$15", 48 | plan: "monthly_15", 49 | text: "an electronic butterfly in a jar.", 50 | index: 7 51 | }, 52 | { 53 | value: "$20", 54 | plan: "monthly_20", 55 | text: "a Super Mario costume, child size.", 56 | index: 8 57 | }, 58 | { 59 | value: "$30", 60 | plan: "monthly_30", 61 | text: "a pair of giant robot slippers with sound.", 62 | index: 9 63 | }, 64 | { 65 | value: "$50", 66 | plan: "monthly_50", 67 | text: "a Han Solo in carbonite rug.", 68 | index: 10 69 | }, 70 | { 71 | value: "$75", 72 | plan: "monthly_75", 73 | text: "a Gandalf pipe prop replica.", 74 | index: 11 75 | }, 76 | { 77 | value: "$100", 78 | plan: "monthly_100", 79 | text: "a pair of sexed Emu chicks.", 80 | index: 12 81 | } 82 | ]; 83 | 84 | export default Ember.View.extend({ 85 | tagName: 'div', 86 | templateName: 'slider', 87 | startValue: null, 88 | currentValue: null, 89 | 90 | updatePlan: function() { 91 | this.set('controller.planValue', plans[this.get('currentValue')-1].value); 92 | this.set('controller.planId', plans[this.get('currentValue')-1].plan); 93 | this.set('controller.planText', plans[this.get('currentValue')-1].text); 94 | }.observes('currentValue'), 95 | 96 | didInsertElement: function() { 97 | var _this = this, 98 | plan = this.get('startValue') ? _.findWhere(plans, {plan: this.get('startValue')}) : plans[4]; 99 | 100 | _this.set('currentValue', plan.index+1); 101 | this.$('.slider').slider({ 102 | range: "min", 103 | step: 1, 104 | min: 1, 105 | max: 13, 106 | value: _this.get('currentValue'), 107 | slide: function( event, ui ) { 108 | _this.set('currentValue', ui.value); 109 | } 110 | }); 111 | } 112 | }); -------------------------------------------------------------------------------- /ember/app/styles/entries.styl: -------------------------------------------------------------------------------- 1 | .d-entry-sidebar 2 | .list-group-item 3 | padding 0 4 | background transparent 5 | a 6 | padding 10px 15px 7 | display block 8 | color rgba(black, 0.65) 9 | .badge 10 | float right 11 | &:hover 12 | background rgba($highlight-color, 0.1) 13 | cursor pointer 14 | .badge 15 | background $highlight-color 16 | border-color $highlight-color 17 | color white 18 | &.active 19 | background $highlight-color 20 | cursor default 21 | background-image url($texture-url) 22 | color white 23 | a 24 | color white 25 | i 26 | display block 27 | .badge 28 | background white 29 | border-color white 30 | color $highlight-color 31 | &:first-child 32 | a 33 | border-radius 2px 2px 0 0 34 | &:last-child 35 | a 36 | border-radius 0 0 2px 2px 37 | 38 | .d-no-entries 39 | text-align center 40 | padding 150px 0 41 | .d-one 42 | font-size 30px 43 | .d-two 44 | font-size 20px 45 | .d-or 46 | margin $base-padding 0 47 | color $highlight-color 48 | font-size 20px 49 | font-weight bold 50 | 51 | .d-entry-show 52 | .d-entry-list_item 53 | .d-what 54 | font-size 20px 55 | line-height 30px 56 | p 57 | line-height 30px 58 | ul 59 | margin-bottom 15px 60 | list-style disc 61 | &:last-child 62 | margin-bottom 0 63 | 64 | .d-entry-list_item 65 | background #FFF 66 | box-shadow 0 0 1px rgba(0,0,0,0.4) 67 | overflow hidden 68 | padding 50px 40px 35px 40px 69 | border-top 1px solid #EFEFEF 70 | position relative 71 | &:first-child 72 | border-top none 73 | .d-what 74 | padding-top 20px 75 | .d-when 76 | font-size 16px 77 | color $primary-color 78 | .d-when-subheader 79 | color #CCC 80 | font-size 14px 81 | .d-actions 82 | position absolute 83 | top 10px 84 | right 10px 85 | display none 86 | .btn 87 | font-size 10px 88 | padding 4px 8px 89 | opacity 0.75 90 | &:hover 91 | opacity 1 92 | &:hover 93 | .d-actions 94 | display block 95 | 96 | .d-new-entry 97 | textarea 98 | border none 99 | box-shadow none 100 | outline none 101 | background-color transparent 102 | width 100% 103 | min-height 250px 104 | resize none 105 | padding 0 106 | .d-error-box 107 | margin-top 20px 108 | 109 | .d-load-more-entries 110 | display block 111 | margin 25px 0 $base-padding 0 112 | 113 | .entry-show 114 | .d-what 115 | font-size 20px 116 | line-height 28px 117 | p 118 | font-size 20px 119 | line-height 30px 120 | margin-bottom 25px 121 | &:last-child 122 | margin-bottom 0 -------------------------------------------------------------------------------- /ember/app/styles/users.styl: -------------------------------------------------------------------------------- 1 | .d-auth-form 2 | margin 0 auto 3 | max-width 350px 4 | text-align center 5 | 6 | .d-error 7 | background #FFF 8 | display inline-block 9 | color $red 10 | margin-bottom 15px 11 | font-size 13px 12 | padding 2px 5px 13 | border-radius 3px 14 | 15 | input 16 | width 100% 17 | font-size 20px 18 | margin-bottom 15px 19 | font-weight 300 20 | border-radius 5px 21 | padding 7px 12px 22 | border none 23 | line-height 36px 24 | box-shadow inset 0 0 2px #666 25 | 26 | .d-btn 27 | width 100% 28 | border none 29 | text-align center 30 | border-radius 5px 31 | color $highlight-color 32 | padding 7px 0 33 | line-height 36px 34 | font-size 20px 35 | opacity 1 36 | box-shadow 0 0 3px #111 37 | background-color white 38 | border-bottom 2px solid $highlight-color 39 | &:hover 40 | background $action-color 41 | color white 42 | border-bottom-color $action-color - 25% 43 | 44 | .d-links 45 | margin-top 20px 46 | a 47 | color white 48 | opacity 0.75 49 | text-shadow #000 0 1px 1px 50 | font-weight 300 51 | font-size 16px 52 | &:hover 53 | opacity 1 54 | text-decoration underline 55 | 56 | .d-plans 57 | .d-top 58 | text-align center 59 | margin 15px 0 70px 0 60 | .d-features 61 | li 62 | max-width 800px 63 | width 100% 64 | margin 30px auto 20px auto 65 | text-align left 66 | display block 67 | p 68 | margin-bottom 8px 69 | .ui-widget-content 70 | margin 15px auto 30px auto 71 | max-width 250px 72 | .ui-widget-header 73 | background $purple 74 | .ui-slider-handle 75 | cursor pointer 76 | 77 | .d-plan-option 78 | font-size 40px 79 | background-image url($contrast-texture-url) 80 | background-color #222 81 | display inline-block 82 | padding 15px 20px 83 | border-radius 5px 84 | box-shadow 0 0 2px #333 85 | color #FFF 86 | cursor default 87 | span 88 | font-size 14px 89 | display block 90 | 91 | .d-plan-text 92 | margin 15px 0 0 0 93 | font-weight bold 94 | font-size 12px 95 | small 96 | font-weight normal 97 | display block 98 | line-height 8px 99 | margin-bottom 20px 100 | 101 | 102 | .d-settings 103 | 104 | .email-times 105 | margin-top 20px 106 | 107 | .d-input 108 | label 109 | text-transform capitalize 110 | 111 | 112 | 113 | .d-importer 114 | textarea 115 | background transparent 116 | border none 117 | width 100% 118 | margin-top 30px 119 | max-height 1000px 120 | overflow auto 121 | .d-processing 122 | padding 20px 25px 123 | background #FFF 124 | border 1px solid #DDD 125 | border-radius 2px 126 | margin-top 20px 127 | display none 128 | p 129 | margin 5px 0 130 | &.on 131 | display inline-block 132 | -------------------------------------------------------------------------------- /rails/app/models/entry.rb: -------------------------------------------------------------------------------- 1 | class Entry < ActiveRecord::Base 2 | include ActionView::Helpers::DateHelper 3 | include ActionView::Helpers::NumberHelper 4 | include PgSearch 5 | pg_search_scope :search_by_body, :against => :body 6 | 7 | belongs_to :user 8 | 9 | ############### 10 | # VALIDATIONS # 11 | ############### 12 | 13 | validates :user, presence: true 14 | validates :entry_date, presence: true 15 | validates :body, presence: true, length: { maximum: 50000 } 16 | 17 | validate :unique_entry_date 18 | 19 | # A user may only have one entry per day 20 | def unique_entry_date 21 | if user && user.entries 22 | existing = user.entries.select('id, entry_date').where("entry_date = ? AND id != ?", entry_date, id).first 23 | end 24 | if existing 25 | errors.add(:entry, "already exists for this date") 26 | end 27 | end 28 | 29 | ############# 30 | # ACCESSORS # 31 | ############# 32 | 33 | def unencrypted_body 34 | if encrypted_body 35 | encrypted_body 36 | else 37 | body 38 | end 39 | end 40 | 41 | ########### 42 | # HELPERS # 43 | ########### 44 | 45 | def prev_entry 46 | entry = Entry.select('id, entry_date').where("user_id = ? AND entry_date < ?", user_id, entry_date).order("entry_date DESC").first 47 | entry ? entry.entry_date : nil 48 | end 49 | 50 | def random_entry 51 | entry = Entry.select('id, entry_date').where("user_id = ? AND entry_date != ?", user_id, entry_date).order("RANDOM()").first 52 | entry ? entry.entry_date : nil 53 | end 54 | 55 | def next_entry 56 | entry = Entry.select('id, entry_date').where("user_id = ? AND entry_date > ?", user_id, entry_date).order("entry_date ASC").first 57 | entry ? entry.entry_date : nil 58 | end 59 | 60 | def time_ago(user) 61 | now = Time.now.in_time_zone(user.time_zone) 62 | date = Date.parse(entry_date.to_s) 63 | if date.day == 29 && date.month == 2 && now.year - 4 == date.year && now.day == 29 && now.month == 2 64 | "Last leap day, exactly 4 years" 65 | elsif now.month == date.month && now.day == date.day && now.year - 1 == date.year 66 | "Exactly 1 year" 67 | elsif now.month - 1 == date.month && now.day == date.day && now.year == date.year 68 | "Exactly 1 month" 69 | elsif now.month == date.month && now.day - 7 == date.day && now.year == date.year 70 | "Exactly 1 week" 71 | else 72 | in_words = distance_of_time_in_words(date, now).capitalize 73 | in_words.to_s.include?("Over") ? "Exactly #{number_with_delimiter((now - date).to_i / 1.day)} days" : in_words 74 | end 75 | end 76 | 77 | def sanitized_body 78 | body_sanitized = ActionView::Base.full_sanitizer.sanitize(self.body) 79 | body_sanitized.gsub!(/\A(\n\n)/,"") if body_sanitized 80 | body_sanitized.gsub!(/(\<\n\n>)\z/,"") if body_sanitized 81 | body_sanitized 82 | end 83 | 84 | # Runtime search.. This assumes users have < crazy # of entries. 85 | # def self.search(user, term) 86 | # hits = [] 87 | # term.downcase! 88 | # user.entries.each do |e| 89 | # if e.body.downcase.include?(term) 90 | # hits.push e.id 91 | # end 92 | # end 93 | # Entry.where(id: hits) 94 | # end 95 | 96 | end 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ###   An alternative to [OhLife][ol] 3 | 4 | ## What is it? 5 | 6 | [DayJot][dj] is a simple, private journaling app. It's built to provide many features that were included in the (now defunct) [OhLife][ol] journaling app. The [hosted version][dj] of [DayJot][dj] will be run forever. As long as there is a single user paying $1/month, it will remain online. 7 | 8 | ## What does it do? 9 | 10 | * Customizable email reminder system. 11 | * Reply to reminder to make a post 12 | * Powerful search 13 | * Multiple, beautiful ways to browse your journal. 14 | * Easy [OhLife][ol] importer. 15 | * Easy export of all entries 16 | * Supports Markdown 17 | 18 | ## How do I use it? 19 | 20 | The easiest way is to sign-up at [DayJot][dj] ;). 21 | 22 | To get it running on your own machine, here's what you need to know: 23 | 24 | #### Stack 25 | 26 | * [Ruby 2](https://www.ruby-lang.org/en/) 27 | * [Rails 4](http://rubyonrails.org/) 28 | * [Ember](http://emberjs.com/) 29 | * [Ember-CLI](http://www.ember-cli.com/) 30 | * [PostgreSQL](http://www.postgresql.org/) 31 | * [MailGun](http://mailgun.com/) 32 | * [Delayed Job](https://github.com/collectiveidea/delayed_job) 33 | 34 | #### Requirements 35 | 36 | 1. Install [Node 0.10.x](http://nodejs.org/) 37 | 2. `npm install -g ember-cli` 38 | 3. `npm install -g bower` 39 | 4. Ruby 2.1.4, I recommend installing via [RVM](http://rvm.io) 40 | 5. `gem install foreman` 41 | 5. [PostgreSQL](http://postgresapp.com) 42 | 43 | [This script][dep] provides an easy way to install the above dependencies. 44 | 45 | #### Running [DayJot][dj] 46 | 47 | After you have cloned this repo, and installed the dependencies above, run this setup script to set up your machine with the necessary dependencies to run and test DayJot: 48 | 49 | % cd rails && ./bin/setup 50 | 51 | After setup, you can run the application from the rails directory with: 52 | 53 | % foreman start -f Procfile.local 54 | 55 | You should be set to use the app at `http://localhost:3000`. 56 | 57 | Relevant config variables can be found in the rails/config/application.yml file. 58 | 59 | Depending on your PostgreSQL setup, you may need to update the DB_USERNAME and DB_PASSWORD properties in `rails/config/application.yml`. 60 | 61 | ## How do I Contribute? 62 | 63 | Gee I'm so glad you asked that, great question! 64 | 65 | 1. Fork 66 | 2. Hack 67 | 3. Test (Rails tests can be run via rspec; Tests haven't been setup for Ember) 68 | 4. Send a pull request. 69 | 70 | The biggest issue right now is that there are very few tests (none for Ember and just a skeleton for Rails). Coincidentally, writing tests is one of the best ways to familiarize oneself with a new codebase. 71 | 72 | ### Gift for contributors 73 | 74 | I would be very grateful to anybody who's up for writing tests for this project. In fact, for anybody who submits a PR with one or more tests (one is fine!), I'll set you up on [DayJot][dj] for free, for life. 75 | 76 | ## Contact 77 | 78 | Please direct any questions, concerns, or general chat to hi@dayjot.com. I'm also available on Twitter at [@marbemac][mb]. 79 | 80 | ## License 81 | 82 | [DayJot][dj] is released under the GNU V2 License. 83 | 84 | [dj]: https://dayjot.com 85 | [mb]: http://twitter.com/marbemac 86 | [ol]: http://ohlife.com 87 | [dep]: https://github.com/thoughtbot/laptop 88 | -------------------------------------------------------------------------------- /ember/app/controllers/plans.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import ENV from 'dayjot/config/environment'; 3 | import Notify from 'ember-notify'; 4 | 5 | export default Ember.Controller.extend({ 6 | processing: false, 7 | stripeKey: ENV.APP.STRIPE_KEY, 8 | errorMessage: null, 9 | redirecting: false, 10 | planValue: null, 11 | planId: null, 12 | planText: null, 13 | 14 | hasPlan: function() { 15 | return this.get('model.plan') ? true : false; 16 | }.property('model.plan'), 17 | 18 | isPlanActive: function() { 19 | return this.get('model.planStatus') === 'active' ? true : false; 20 | }.property('model.planStatus'), 21 | 22 | actions: { 23 | startPlan: function() { 24 | this.startPurchase(this.get('planId'), 'DayJot', this.get('planValue') + ' per month'); 25 | }, 26 | updatePlan: function() { 27 | this.updatePlan(this.get('planId')); 28 | } 29 | }, 30 | 31 | startPurchase: function(plan, name, description) { 32 | var _this = this; 33 | 34 | Ember.run.next(function() { 35 | StripeCheckout.open({ 36 | key: _this.get('stripeKey'), 37 | name: name, 38 | description: description, 39 | allowRememberMe: false, 40 | email: _this.get('session.currentUser.email'), 41 | panelLabel: "Subscribe", 42 | token: function(result) { 43 | _this.sendPurchase(plan, result.id); 44 | } 45 | }); 46 | }); 47 | }, 48 | 49 | sendPurchase: function(plan, token) { 50 | var _this = this, 51 | user = this.get('session.currentUser'); 52 | 53 | this.set('processing', true); 54 | Ember.$.ajax({ 55 | url: ENV.APP.API_HOST + "/update_plan", 56 | type: "POST", 57 | data: {plan: plan, token: token}, 58 | dataType: 'json', 59 | success: function() { 60 | _this.set('processing', false); 61 | _this.set('redirecting', true); 62 | user.refresh().then(function() { 63 | _this.transitionToRoute('entries'); 64 | Notify.success('Successfully subscribed!'); 65 | setTimeout(function() { 66 | _this.set('redirecting', false); 67 | }, 1000); 68 | }); 69 | }, 70 | error: function(error) { 71 | _this.set('processing', false); 72 | _this.set('errorMessage', error.responseJSON.error); 73 | } 74 | }); 75 | }, 76 | 77 | updatePlan: function(plan) { 78 | var _this = this, 79 | user = this.get('session.currentUser'); 80 | 81 | this.set('processing', true); 82 | Ember.$.ajax({ 83 | url: ENV.APP.API_HOST + "/update_plan", 84 | type: "POST", 85 | data: {plan: plan}, 86 | dataType: 'json', 87 | success: function() { 88 | _this.set('processing', false); 89 | _this.set('redirecting', true); 90 | user.refresh().then(function() { 91 | _this.transitionToRoute('entries'); 92 | Notify.success('Subscription updated.'); 93 | setTimeout(function() { 94 | _this.set('redirecting', false); 95 | }, 1000); 96 | }); 97 | }, 98 | error: function(error) { 99 | _this.set('processing', false); 100 | _this.set('errorMessage', error.responseJSON.error); 101 | } 102 | }); 103 | } 104 | 105 | }); -------------------------------------------------------------------------------- /rails/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | config.force_ssl = true 43 | 44 | # Set to :debug to see everything in the log. 45 | config.log_level = :info 46 | 47 | # Prepend all log lines with the following tags. 48 | # config.log_tags = [ :subdomain, :uuid ] 49 | 50 | # Use a different logger for distributed setups. 51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 52 | 53 | # Use a different cache store in production. 54 | # config.cache_store = :mem_cache_store 55 | 56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 57 | # config.action_controller.asset_host = "http://assets.example.com" 58 | 59 | config.action_mailer.default_url_options = { :host => 'https://dayjot.com' } 60 | 61 | # Ignore bad email addresses and do not raise email delivery errors. 62 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 63 | # config.action_mailer.raise_delivery_errors = false 64 | 65 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 66 | # the I18n.default_locale when a translation cannot be found). 67 | config.i18n.fallbacks = true 68 | 69 | # Send deprecation notices to registered listeners. 70 | config.active_support.deprecation = :notify 71 | 72 | # Disable automatic flushing of the log to improve performance. 73 | # config.autoflush_log = false 74 | 75 | # Use default logging formatter so that PID and timestamp are not suppressed. 76 | config.log_formatter = ::Logger::Formatter.new 77 | 78 | # Do not dump schema after migrations. 79 | config.active_record.dump_schema_after_migration = false 80 | end 81 | -------------------------------------------------------------------------------- /rails/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20141221035442) do 15 | 16 | # These are extensions that must be enabled in order to support this database 17 | enable_extension "plpgsql" 18 | enable_extension "hstore" 19 | enable_extension "uuid-ossp" 20 | 21 | create_table "delayed_jobs", force: true do |t| 22 | t.integer "priority", default: 0, null: false 23 | t.integer "attempts", default: 0, null: false 24 | t.text "handler", null: false 25 | t.text "last_error" 26 | t.datetime "run_at" 27 | t.datetime "locked_at" 28 | t.datetime "failed_at" 29 | t.string "locked_by" 30 | t.string "queue" 31 | t.datetime "created_at" 32 | t.datetime "updated_at" 33 | end 34 | 35 | add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree 36 | 37 | create_table "entries", id: :uuid, default: "uuid_generate_v4()", force: true do |t| 38 | t.binary "encrypted_body" 39 | t.text "body" 40 | t.date "entry_date" 41 | t.string "source", default: "web" 42 | t.uuid "user_id" 43 | t.datetime "created_at" 44 | t.datetime "updated_at" 45 | end 46 | 47 | add_index "entries", ["user_id", "entry_date"], name: "index_entries_on_user_id_and_entry_date", using: :btree 48 | 49 | create_table "users", id: :uuid, default: "uuid_generate_v4()", force: true do |t| 50 | t.string "email", default: "", null: false 51 | t.string "encrypted_password", default: "", null: false 52 | t.string "reset_password_token" 53 | t.datetime "reset_password_sent_at" 54 | t.datetime "remember_created_at" 55 | t.integer "sign_in_count", default: 0, null: false 56 | t.datetime "current_sign_in_at" 57 | t.datetime "last_sign_in_at" 58 | t.string "current_sign_in_ip" 59 | t.string "last_sign_in_ip" 60 | t.datetime "created_at" 61 | t.datetime "updated_at" 62 | t.string "authentication_token" 63 | t.hstore "email_times" 64 | t.string "email_key" 65 | t.datetime "last_export_time" 66 | t.string "plan" 67 | t.datetime "plan_started" 68 | t.datetime "plan_canceled" 69 | t.string "plan_status", default: "needs_card" 70 | t.string "stripe_customer_id" 71 | t.string "last_4_digits" 72 | t.string "time_zone", default: "US/Pacific" 73 | t.string "status", default: "active" 74 | t.integer "reminder_emails_sent", default: 0 75 | t.datetime "last_reminder_sent_at" 76 | t.datetime "trial_end" 77 | t.boolean "include_email_memory", default: true 78 | end 79 | 80 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree 81 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree 82 | 83 | end 84 | -------------------------------------------------------------------------------- /ember/app/styles/pikaday.styl: -------------------------------------------------------------------------------- 1 | .pika-single 2 | z-index 9999 3 | display block 4 | position relative 5 | color #333 6 | background #fff 7 | border 1px solid #ccc 8 | border-bottom-color #bbb 9 | font-family "Helvetica Neue", Helvetica, Arial, sans-serif 10 | 11 | .pika-single:before, 12 | .pika-single:after 13 | content " " 14 | display table 15 | 16 | .pika-single 17 | *zoom 1 18 | &:after 19 | clear both 20 | &.is-hidden 21 | display none 22 | &.is-bound 23 | position absolute 24 | box-shadow 0 5px 15px -5px rgba(0,0,0,.5) 25 | 26 | .pika-lendar 27 | float left 28 | width 240px 29 | margin 8px 30 | 31 | .pika-title 32 | position relative 33 | text-align center 34 | select 35 | cursor pointer 36 | position absolute 37 | z-index 9998 38 | margin 0 39 | left 0 40 | top 5px 41 | opacity 0 42 | 43 | .pika-label 44 | display inline-block 45 | *display inline 46 | position relative 47 | z-index 9999 48 | overflow hidden 49 | margin 0 50 | padding 5px 3px 51 | font-size 14px 52 | line-height 20px 53 | font-weight bold 54 | background-color #fff 55 | 56 | .pika-prev, 57 | .pika-next 58 | display block 59 | cursor pointer 60 | position relative 61 | outline none 62 | border 0 63 | padding 0 64 | width 20px 65 | height 30px 66 | text-indent 20px 67 | white-space nowrap 68 | overflow hidden 69 | background-color transparent 70 | background-position center center 71 | background-repeat no-repeat 72 | background-size 75% 75% 73 | opacity .5 74 | *position absolute 75 | *top 0 76 | 77 | .pika-prev:hover, 78 | .pika-next:hover 79 | opacity 1 80 | 81 | .pika-prev, 82 | .is-rtl .pika-next 83 | float left 84 | background-image url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==') 85 | *left 0 86 | 87 | .pika-next, 88 | .is-rtl .pika-prev 89 | float right 90 | background-image url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=') 91 | *right 0 92 | 93 | .pika-prev.is-disabled, 94 | .pika-next.is-disabled 95 | cursor default 96 | opacity .2 97 | 98 | .pika-select 99 | display inline-block 100 | *display inline 101 | 102 | .pika-table 103 | width 100% 104 | border-collapse collapse 105 | border-spacing 0 106 | border 0 107 | th 108 | color #999 109 | font-size 12px 110 | line-height 25px 111 | font-weight bold 112 | text-align center 113 | 114 | .pika-table th, 115 | .pika-table td 116 | width 14.285714285714286% 117 | padding 0 118 | 119 | .pika-button 120 | cursor pointer 121 | display block 122 | box-sizing border-box 123 | -moz-box-sizing border-box 124 | outline none 125 | border 0 126 | margin 0 127 | width 100% 128 | padding 5px 129 | color #666 130 | font-size 12px 131 | line-height 15px 132 | text-align right 133 | background #f5f5f5 134 | &:hover 135 | color #fff !important 136 | background #ff8000 !important 137 | box-shadow none !important 138 | border-radius 3px !important 139 | 140 | .pika-week 141 | font-size 11px 142 | color #999 143 | 144 | .is-today 145 | .pika-button 146 | color #33aaff 147 | font-weight bold 148 | 149 | .is-selected 150 | .pika-button 151 | color #fff 152 | font-weight bold 153 | background #33aaff 154 | box-shadow inset 0 1px 3px #178fe5 155 | border-radius 3px 156 | 157 | .is-disabled 158 | .pika-button 159 | pointer-events none 160 | cursor default 161 | color #999 162 | opacity .3 163 | 164 | -------------------------------------------------------------------------------- /ember/app/styles/home.styl: -------------------------------------------------------------------------------- 1 | .d-home 2 | .row 3 | margin-left 0 4 | margin-right 0 5 | .d-top-half 6 | text-align center 7 | background-image url($contrast-texture-url) 8 | background-color #191919 9 | padding 50px 0 10 | .d-divider 11 | height 20px 12 | box-shadow 0 0 4px #333 13 | background-image url($contrast-texture-url) 14 | background-color $highlight-color 15 | &.muted 16 | height 10px 17 | box-shadow 0 0 1px rgba(0,0,0,0.5) 18 | background-image url($texture-url) 19 | background-color rgba(0,0,0,0.1) 20 | .container 21 | max-width 1200px 22 | width 100% 23 | 24 | .d-123 25 | padding 80px 0 26 | text-align center 27 | .d-name 28 | font-size 22px 29 | font-weight bold 30 | color $primary-color 31 | margin-bottom 10px 32 | p 33 | font-weight 300 34 | margin-bottom 8px 35 | font-size 16px 36 | line-height 22px 37 | 38 | 39 | .d-feature-container 40 | padding 70px 0 41 | background-color rgba(0,0,0,0.04) 42 | img 43 | width 100% 44 | display none 45 | box-shadow 0 0 2px rgba(0,0,0,0.5) 46 | border-radius 7px 47 | &.on 48 | display block 49 | .d-title 50 | font-size 24px 51 | font-weight 300 52 | margin 0 0 60px 53 | text-align center 54 | text-transform uppercase 55 | color rgba(0,0,0,0.8) 56 | .d-features 57 | padding-top 15px 58 | li 59 | opacity 0.3 60 | margin-bottom 50px 61 | cursor pointer 62 | position relative 63 | padding-left 100px 64 | &:last-child 65 | margin-bottom 0 66 | &:hover 67 | opacity 0.75 68 | &.on 69 | opacity 1 70 | cursor default 71 | p 72 | display block 73 | h2 74 | font-size 22px 75 | font-weight bold 76 | margin-bottom 10px 77 | p 78 | font-weight 300 79 | margin-bottom 8px 80 | font-size 16px 81 | line-height 22px 82 | display none 83 | i 84 | border-radius 50% 85 | color white 86 | font-size 18px 87 | margin-top -20px 88 | padding 12px 89 | position absolute 90 | top 50% 91 | left 40px 92 | &.blue 93 | background-color #3498db 94 | &.green 95 | background-color #2ecc71 96 | &.yellow 97 | background-color #f1c40f 98 | &.teal 99 | background-color #1abc9c 100 | &.orange 101 | background-color #e67e22 102 | 103 | .d-open-source 104 | text-align center 105 | padding 80px 0 106 | i 107 | font-size 30px 108 | padding 20px 109 | color white 110 | display inline-block 111 | background-color #34495e 112 | border-radius 50% 113 | p 114 | font-size 22px 115 | margin-top 40px 116 | font-weight 300 117 | color rgba(0,0,0,0.75) 118 | line-height 34px 119 | 120 | .d-home-nav 121 | position relative 122 | z-index 10 123 | margin-top -1 * $header-spacing 124 | .navbar-default 125 | margin-bottom 0 126 | box-shadow none 127 | .navbar-toggle 128 | border-color white 129 | font-size 16px 130 | padding 3px 8px 131 | color white 132 | &:hover 133 | color $action-color 134 | .navbar-header 135 | margin 0 136 | .navbar 137 | background-image url($contrast-texture-url) 138 | background-color #191919 139 | position relative 140 | .d-logo 141 | a 142 | color white 143 | span 144 | background-color white 145 | color $highlight-color 146 | .nav 147 | li 148 | a 149 | color white 150 | &:hover 151 | text-decoration underline 152 | background-color transparent !important 153 | color white 154 | &.active 155 | color white 156 | &:hover 157 | color white 158 | text-decoration none 159 | -------------------------------------------------------------------------------- /rails/app/controllers/entries_controller.rb: -------------------------------------------------------------------------------- 1 | class EntriesController < ApplicationController 2 | before_action :authenticate_user!, except: [:handle_email] 3 | before_action :set_entry, only: [:show, :update, :destroy] 4 | 5 | def index 6 | @entries = current_user.entries 7 | 8 | if params[:search].present? 9 | @entries = current_user.entries.search_by_body(params[:search]) 10 | end 11 | 12 | if params[:when].present? 13 | @entries = @entries.where("to_char(entry_date,'YYYY-MM') = ?", params[:when]) 14 | end 15 | 16 | page = params[:page].present? ? params[:page] : 1 17 | @entries = @entries.page(page).per(10) 18 | 19 | render json: @entries.order('entry_date DESC') 20 | end 21 | 22 | def show 23 | unless @entry 24 | entry_date = Time.now.strftime('%Y-%m-%d') 25 | @entry = Entry.new(entry_date: entry_date, user_id: current_user.id) 26 | end 27 | meta = { current_entry: @entry.entry_date, prev_entry: @entry.prev_entry, random_entry: @entry.random_entry, next_entry: @entry.next_entry } 28 | 29 | if @entry.persisted? 30 | render json: { entry: @entry.as_json, meta: meta } 31 | else 32 | render json: { meta: meta }, status: 404 33 | end 34 | end 35 | 36 | def create 37 | @entry = current_user.entries.new(entry_params) 38 | if @entry.save 39 | render json: @entry 40 | else 41 | render json: { errors: @entry.errors.full_messages }, status: :unprocessable_entity 42 | end 43 | end 44 | 45 | def update 46 | @entry.update(entry_params) 47 | render json: @entry 48 | end 49 | 50 | def destroy 51 | @entry.destroy 52 | render json: @entry 53 | end 54 | 55 | def destroy_all 56 | current_user.entries.delete_all 57 | render json: {} 58 | end 59 | 60 | def export 61 | if current_user.can_export? 62 | UserMailer.delay.export_entries(current_user.id) 63 | render json: {} 64 | else 65 | render json: { error: 'You may only export your entries once every 24 hours.' }, status: 400 66 | end 67 | end 68 | 69 | # Handles incoming emails from Mailchimp 70 | def handle_email 71 | token = params['recipient'].split('@')[0] 72 | user = User.where(email_key: token).first 73 | 74 | # TODO: Notify the user that they couldn't be found.. 75 | unless user 76 | render json: { error: "User not found for recipient #{params['recipient']} and token #{token}." } 77 | return 78 | end 79 | 80 | # Parse out the entry date from the email 81 | # If not found, use today's date 82 | entry_date_regex = /Reply\ with\ entry\ for\ date:\ ([\d]{4}-[\d]{2}-[\d]{2})\./ 83 | m = params['body-plain'].match(entry_date_regex) 84 | if m && m.captures[0] 85 | entry_date = m.captures[0] 86 | else 87 | entry_date = Time.now.in_time_zone(user.time_zone).strftime('%Y-%m-%d') 88 | end 89 | 90 | # Find or create the entry 91 | entry = user.entries.where(entry_date: entry_date).first 92 | unless entry 93 | entry = Entry.new(user_id: user.id, entry_date: entry_date, source: 'email') 94 | end 95 | 96 | # If the entry already exists, append the new content onto the body 97 | if entry.persisted? 98 | entry.body = entry.body + '\n\n' + params['stripped-text'] 99 | else 100 | entry.body = params['stripped-text'] 101 | end 102 | 103 | if entry.save 104 | render json: entry 105 | else 106 | render json: { errors: entry.errors.full_messages } 107 | end 108 | end 109 | 110 | private 111 | 112 | def set_entry 113 | # if the id is in the format YYYY-MM-DD, search on entry date, else search by id 114 | if /[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}/.match(params[:id]) 115 | @entry = current_user.entries.where(entry_date: params[:id]).first 116 | else 117 | @entry = current_user.entries.find(params[:id]) 118 | end 119 | end 120 | 121 | def entry_params 122 | params.require(:entry).permit(:body, :entry_date) 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /ember/app/controllers/importer.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | 3 | export default Ember.Controller.extend({ 4 | importText: null, 5 | entryCount: null, 6 | done: false, 7 | successCount: 0, 8 | processing: false, 9 | processingText: null, 10 | processingCount: 0, 11 | errors: null, 12 | 13 | actions: { 14 | resetImport: function() { 15 | this.set('importText', ""); 16 | this.set('entryCount', null); 17 | this.set('successCount', 0); 18 | this.set('processingText', null); 19 | this.set('processingCount', 0); 20 | this.set('errors', null); 21 | this.set('done', false); 22 | this.set('processing', false); 23 | }, 24 | startImport: function() { 25 | this.set('processingText', "Processing import text.."); 26 | this.set('processing', true); 27 | 28 | var lines = this.get('importText').split('\n'), 29 | line = null, 30 | entries = [], 31 | currentEntry = null, 32 | entryDate = null, 33 | i = 0; 34 | 35 | for (i = 0; i < lines.length; i++) { 36 | line = lines[i].trim(); 37 | 38 | // just a newline 39 | if (!line) { 40 | // if we have an entry right now, add the newline to it 41 | if (currentEntry) { 42 | currentEntry.text = currentEntry.text + "\n"; 43 | } 44 | continue; 45 | } 46 | 47 | entryDate = moment(line, 'YYYY-MM-DD', true); 48 | if (entryDate.isValid()) { 49 | if (currentEntry) { 50 | currentEntry.text = $.trim(currentEntry.text); 51 | entries.push(currentEntry); 52 | } 53 | 54 | currentEntry = { 55 | date: entryDate.utc().toDate(), 56 | text: "" 57 | }; 58 | 59 | // DailyDiary text exports are formatted as such: 60 | // 2015-01-01 61 | // 22:00 62 | // 63 | // Rest of entry... 64 | // 65 | // So if the next line is a time, skip it to support DailyDiary exports. 66 | if (moment(lines[i+1], 'HH:mm', true).isValid()) { 67 | i++; 68 | } 69 | } else { 70 | if (currentEntry) { 71 | currentEntry.text = currentEntry.text + line + "\n"; 72 | } 73 | } 74 | } 75 | 76 | // ran out of lines, need to handle the last entry 77 | // this must happen even if the last line was a newline 78 | if (currentEntry) { 79 | currentEntry.text = $.trim(currentEntry.text); 80 | entries.push(currentEntry); 81 | } 82 | 83 | this.set('entryCount', entries.length); 84 | var entry = null, 85 | entryObjects = []; 86 | for (i = 0; i < entries.length; i++) { 87 | entry = this.store.createRecord('entry', { 88 | entryDate: entries[i].date, 89 | body: entries[i].text 90 | }); 91 | entryObjects.push(entry); 92 | } 93 | 94 | this.saveEntries(entryObjects); 95 | } 96 | }, 97 | 98 | saveEntries: function(entries) { 99 | var _this = this, 100 | currentEntry = entries.shift(); 101 | if (currentEntry) { 102 | currentEntry.save().then(function() { 103 | _this.set('successCount', _this.get('successCount')+1); 104 | }, function(error) { 105 | var errors = _this.get('errors'); 106 | errors = errors ? errors : ''; 107 | errors += "

> "+moment(currentEntry.get('entryDate')).format('YYYY-MM-DD') + ": "+error.error+"

"; 108 | _this.set('errors', errors); 109 | }).finally(function(){ 110 | _this.set('processingCount', _this.get('processingCount')+1); 111 | _this.saveEntries(entries); 112 | }); 113 | } else { 114 | this.set('done', true); 115 | var user = this.get('session.currentUser'); 116 | user.refresh(); 117 | } 118 | } 119 | }); 120 | -------------------------------------------------------------------------------- /ember/app/templates/plans.hbs: -------------------------------------------------------------------------------- 1 | {{view "site-nav"}} 2 | 3 |
4 | 5 |
6 |
7 |
Become a Member. What's it worth to you?
8 |
9 | 10 | {{#if redirecting}} 11 |

Success! Redirecting...

12 | {{else}} 13 | {{#if processing}} 14 |

Processing...

15 | {{else}} 16 |
17 |
{{planValue}} per month
18 | 19 |

20 | The same price as 21 | {{planText}} 22 |

23 | {{view "slider" startValue=session.currentUser.plan}} 24 | 25 | {{#if session.currentUser}} 26 | {{#if hasPlan}} 27 | {{#if isPlanActive}} 28 | 29 | {{else}} 30 | 31 | {{/if}} 32 | {{else}} 33 | 34 | 35 | {{#if session.currentUser.trialActive}} 36 | {{#link-to "entries" class='btn btn-default'}}... or do this later{{/link-to}} 37 | {{/if}} 38 | 39 | {{/if}} 40 | 41 | {{#if errorMessage}} 42 |
43 |

{{errorMessage}}

44 |
45 | {{/if}} 46 | {{else}} 47 | {{#link-to "index" class='btn btn-primary'}}Register First{{/link-to}} 48 | {{/if}} 49 |
50 | 51 |
    52 |
  • 53 |

    30 Day Trial

    54 | {{#if session.currentUser}} 55 | {{#unless session.currentUser.planActive}} 56 | {{#unless session.currentUser.trialActive}} 57 |

    Hmm, looks like your trial is expired!

    58 | {{/unless}} 59 | {{/unless}} 60 | {{/if}} 61 |

    Use DayJot free for a month, no credit card required. {{#link-to "entries"}}Click here{{/link-to}} to carry on.

    62 |

    If you're not happy or something's missing, let us know at hi@dayjot.com

    63 |
  • 64 | 65 |
  • 66 |

    You're charging money. Why?

    67 |

    DayJot has an open and up-front business model. You pay money, and we keep your memories online forever.

    68 |

    There's no guess work about how the site will stay up, or whether your data is yours.

    69 |

    DayJot was built to run at minimal cost, and to scale well.

    70 |

    As long as there is a single paying user, DayJot will be online, and this won't happpen.

    71 |
  • 72 |
  • 73 |

    Security

    74 |

    All credit card info is handled securely with Stripe.

    75 |

    All data sent to and from DayJot is encrypted and stored in a secure location.

    76 |

    The DayJot database itself is encrypted, so even if an attacker got access to it, they could not read the information.

    77 |
  • 78 |
  • 79 |

    Privacy

    80 |

    Your information will never be shared or sold.

    81 |

    Only you can view your entries.

    82 |

    No Facebook. No Twitter. By design, DayJot is not a social website.

    83 |
  • 84 |
  • 85 |

    Questions?

    86 |

    If you have any questions or issues, please email hi@dayjot.com.

    87 |
  • 88 |
89 | {{/if}} 90 | {{/if}} 91 | 92 |
93 |
-------------------------------------------------------------------------------- /rails/config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | omniauth_callbacks: 27 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 28 | success: "Successfully authenticated from %{kind} account." 29 | passwords: 30 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 31 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 32 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 33 | updated: "Your password has been changed successfully. You are now signed in." 34 | updated_not_active: "Your password has been changed successfully." 35 | registrations: 36 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 37 | signed_up: "Welcome! You have signed up successfully." 38 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 39 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 40 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 41 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." 42 | updated: "Your account has been updated successfully." 43 | sessions: 44 | signed_in: "Signed in successfully." 45 | signed_out: "Signed out successfully." 46 | already_signed_out: "Signed out successfully." 47 | unlocks: 48 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 49 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 50 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 51 | errors: 52 | messages: 53 | already_confirmed: "was already confirmed, please try signing in" 54 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 55 | expired: "has expired, please request a new one" 56 | not_found: "not found" 57 | not_locked: "was not locked" 58 | not_saved: 59 | one: "1 error prohibited this %{resource} from being saved:" 60 | other: "%{count} errors prohibited this %{resource} from being saved:" 61 | -------------------------------------------------------------------------------- /ember/app/controllers/settings.js: -------------------------------------------------------------------------------- 1 | import Ember from "ember"; 2 | import Notify from 'ember-notify'; 3 | import ENV from 'dayjot/config/environment'; 4 | 5 | export default Ember.ObjectController.extend({ 6 | errorMessage: null, 7 | saving: false, 8 | canceling: false, 9 | emailTimesUpdated: null, 10 | correctedEmailTimes: {}, 11 | exporting: false, 12 | deleting: false, 13 | 14 | planInDollars: function() { 15 | return this.get('model.plan').split('_')[1]; 16 | }.property('plan'), 17 | 18 | zones: function() { 19 | var zones = ['Iceland','Europe/London','Europe/Madrid','Europe/Athens','Asia/Tehran','Europe/Moscow', 20 | 'Asia/Kabul','Asia/Calcutta','Indian/Chagos','Asia/Bangkok','Asia/Shanghai','Asia/Tokyo', 21 | 'Pacific/Guam','Australia/Melbourne','Pacific/Fiji','Pacific/Auckland','Pacific/Apia', 22 | 'Atlantic/Cape_Verde','Brazil/East','US/Eastern','US/Central','US/Mountain','US/Pacific', 23 | 'US/Alaska','US/Aleutian','Pacific/Honolulu','Pacific/Midway']; 24 | var zoneData = []; 25 | var now; 26 | for (var i = 0; i < zones.length; i++) { 27 | now = moment().tz(zones[i]); 28 | zoneData.push({ 29 | 'name': zones[i]+', '+now.format('h:mma'), 30 | 'offset': now.format('Z').split(':')[0], 31 | 'value': zones[i] 32 | }); 33 | } 34 | zoneData = _.sortBy(zoneData, 'offset'); 35 | return zoneData; 36 | }.property(), 37 | 38 | actions: { 39 | cancel: function() { 40 | this.cancel(); 41 | }, 42 | saveEmailSettings: function() { 43 | var _this = this; 44 | this.set('saving', true); 45 | this.get('model').save().then(function() { 46 | _this.set('saving', null); 47 | Notify.success("Settings saved.", {closeAfter: 3000}); 48 | }, function(err) { 49 | _this.set('saving', null); 50 | Notify.error(err.message, {closeAfter: 5000}); 51 | }); 52 | }, 53 | export: function() { 54 | var _this = this; 55 | this.set('exporting', true); 56 | Ember.$.ajax({ 57 | url: ENV.APP.API_HOST + '/entries/export', 58 | type: 'POST', 59 | dataType: 'json' 60 | }).then(function() { 61 | _this.set('exporting', false); 62 | Notify.success("Export in progress! Look for an email in the next hour.", {closeAfter: 5000}); 63 | }, function(err) { 64 | _this.set('exporting', false); 65 | Notify.error(err.responseJSON.error, {closeAfter: 5000}); 66 | }); 67 | }, 68 | delete: function() { 69 | var r = confirm("Are you sure you want to erase ALL of your entries? This cannot be undone."); 70 | if (r === true) { 71 | var r2 = confirm("Ok.. but really? Are you sure.. just double checking."); 72 | if (r2 === true) { 73 | var _this = this; 74 | this.set('deleting', true); 75 | Ember.$.ajax({ 76 | url: ENV.APP.API_HOST + '/entries', 77 | type: 'DELETE', 78 | dataType: 'json' 79 | }).then(function() { 80 | window.location = "/entries"; 81 | }, function(err) { 82 | _this.set('deleting', false); 83 | Notify.error(err.responseJSON.error, {closeAfter: 5000}); 84 | }); 85 | } 86 | } 87 | } 88 | }, 89 | 90 | cancel: function() { 91 | var r = confirm("Are you sure you want to cancel your subscription?"); 92 | if (r === true) { 93 | var _this = this, 94 | user = this.get('session.currentUser'); 95 | 96 | this.set('canceling', true); 97 | 98 | Ember.$.ajax({ 99 | url: ENV.APP.API_HOST + "/cancel_plan", 100 | type: "POST", 101 | dataType: 'json', 102 | success: function() { 103 | user.refresh().then(function() { 104 | _this.set('canceling', false); 105 | Notify.success("Subscription cancelled."); 106 | }); 107 | }, 108 | error: function(err) { 109 | _this.set('canceling', false); 110 | Notify.error(err.message, {closeAfter: 5000}); 111 | } 112 | }); 113 | } 114 | } 115 | }); -------------------------------------------------------------------------------- /ember/app/styles/github-ribbon.styl: -------------------------------------------------------------------------------- 1 | /*! 2 | * "Fork me on GitHub" CSS ribbon v0.1.1 | MIT License 3 | * https://github.com/simonwhitaker/github-fork-ribbon-css 4 | */ 5 | 6 | /* Left will inherit from right (so we don't need to duplicate code) */ 7 | .github-fork-ribbon { 8 | /* The right and left classes determine the side we attach our banner to */ 9 | position: absolute; 10 | 11 | /* Add a bit of padding to give some substance outside the "stitching" */ 12 | padding: 2px 0; 13 | 14 | /* Set the base colour */ 15 | background-color: $highlight-color; 16 | 17 | /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */ 18 | background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15))); 19 | background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); 20 | background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); 21 | background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); 22 | background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); 23 | background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); 24 | 25 | /* Add a drop shadow */ 26 | -webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); 27 | -moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); 28 | box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); 29 | 30 | /* Set the font */ 31 | font: 700 13px "Helvetica Neue", Helvetica, Arial, sans-serif; 32 | 33 | z-index: 9999; 34 | pointer-events: auto; 35 | } 36 | 37 | .github-fork-ribbon a, 38 | .github-fork-ribbon a:hover { 39 | /* Set the text properties */ 40 | color: #fff; 41 | text-decoration: none; 42 | text-shadow: 0 -1px rgba(0, 0, 0, 0.5); 43 | text-align: center; 44 | 45 | /* Set the geometry. If you fiddle with these you'll also need 46 | to tweak the top and right values in .github-fork-ribbon. */ 47 | width: 200px; 48 | line-height: 20px; 49 | 50 | /* Set the layout properties */ 51 | display: inline-block; 52 | padding: 2px 0; 53 | 54 | /* Add "stitching" effect */ 55 | border-width: 1px 0; 56 | border-style: dotted; 57 | border-color: #fff; 58 | border-color: rgba(255, 255, 255, 0.7); 59 | } 60 | 61 | .github-fork-ribbon-wrapper { 62 | width: 150px; 63 | height: 150px; 64 | position: absolute; 65 | overflow: hidden; 66 | top: 0; 67 | z-index: 9999; 68 | pointer-events: none; 69 | } 70 | 71 | .github-fork-ribbon-wrapper.fixed { 72 | position: fixed; 73 | } 74 | 75 | .github-fork-ribbon-wrapper.left { 76 | left: 0; 77 | } 78 | 79 | .github-fork-ribbon-wrapper.right { 80 | right: 0; 81 | } 82 | 83 | .github-fork-ribbon-wrapper.left-bottom { 84 | position: fixed; 85 | top: inherit; 86 | bottom: 0; 87 | left: 0; 88 | } 89 | 90 | .github-fork-ribbon-wrapper.right-bottom { 91 | position: fixed; 92 | top: inherit; 93 | bottom: 0; 94 | right: 0; 95 | } 96 | 97 | .github-fork-ribbon-wrapper.right .github-fork-ribbon { 98 | top: 42px; 99 | right: -43px; 100 | 101 | -webkit-transform: rotate(45deg); 102 | -moz-transform: rotate(45deg); 103 | -ms-transform: rotate(45deg); 104 | -o-transform: rotate(45deg); 105 | transform: rotate(45deg); 106 | } 107 | 108 | .github-fork-ribbon-wrapper.left .github-fork-ribbon { 109 | top: 42px; 110 | left: -43px; 111 | 112 | -webkit-transform: rotate(-45deg); 113 | -moz-transform: rotate(-45deg); 114 | -ms-transform: rotate(-45deg); 115 | -o-transform: rotate(-45deg); 116 | transform: rotate(-45deg); 117 | } 118 | 119 | 120 | .github-fork-ribbon-wrapper.left-bottom .github-fork-ribbon { 121 | top: 80px; 122 | left: -43px; 123 | 124 | -webkit-transform: rotate(45deg); 125 | -moz-transform: rotate(45deg); 126 | -ms-transform: rotate(45deg); 127 | -o-transform: rotate(45deg); 128 | transform: rotate(45deg); 129 | } 130 | 131 | .github-fork-ribbon-wrapper.right-bottom .github-fork-ribbon { 132 | top: 80px; 133 | right: -43px; 134 | 135 | -webkit-transform: rotate(-45deg); 136 | -moz-transform: rotate(-45deg); 137 | -ms-transform: rotate(-45deg); 138 | -o-transform: rotate(-45deg); 139 | transform: rotate(-45deg); 140 | } 141 | -------------------------------------------------------------------------------- /rails/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause this 4 | # file to always be loaded, without a need to explicitly require it in any files. 5 | # 6 | # Given that it is always loaded, you are encouraged to keep this file as 7 | # light-weight as possible. Requiring heavyweight dependencies from this file 8 | # will add to the boot time of your test suite on EVERY test run, even for an 9 | # individual file that may not need all of that loaded. Instead, consider making 10 | # a separate helper file that requires the additional dependencies and performs 11 | # the additional setup, and require it from the spec files that actually need it. 12 | # 13 | # The `.rspec` file also contains a few flags that are not defaults but that 14 | # users commonly want. 15 | # 16 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 17 | RSpec.configure do |config| 18 | # rspec-expectations config goes here. You can use an alternate 19 | # assertion/expectation library such as wrong or the stdlib/minitest 20 | # assertions if you prefer. 21 | config.expect_with :rspec do |expectations| 22 | # This option will default to `true` in RSpec 4. It makes the `description` 23 | # and `failure_message` of custom matchers include text for helper methods 24 | # defined using `chain`, e.g.: 25 | # be_bigger_than(2).and_smaller_than(4).description 26 | # # => "be bigger than 2 and smaller than 4" 27 | # ...rather than: 28 | # # => "be bigger than 2" 29 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 30 | end 31 | 32 | # rspec-mocks config goes here. You can use an alternate test double 33 | # library (such as bogus or mocha) by changing the `mock_with` option here. 34 | config.mock_with :rspec do |mocks| 35 | # Prevents you from mocking or stubbing a method that does not exist on 36 | # a real object. This is generally recommended, and will default to 37 | # `true` in RSpec 4. 38 | mocks.verify_partial_doubles = true 39 | end 40 | 41 | # The settings below are suggested to provide a good initial experience 42 | # with RSpec, but feel free to customize to your heart's content. 43 | =begin 44 | # These two settings work together to allow you to limit a spec run 45 | # to individual examples or groups you care about by tagging them with 46 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 47 | # get run. 48 | config.filter_run :focus 49 | config.run_all_when_everything_filtered = true 50 | 51 | # Limits the available syntax to the non-monkey patched syntax that is recommended. 52 | # For more details, see: 53 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 54 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 55 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 56 | config.disable_monkey_patching! 57 | 58 | # Many RSpec users commonly either run the entire suite or an individual 59 | # file, and it's useful to allow more verbose output when running an 60 | # individual spec file. 61 | if config.files_to_run.one? 62 | # Use the documentation formatter for detailed output, 63 | # unless a formatter has already been configured 64 | # (e.g. via a command-line flag). 65 | config.default_formatter = 'doc' 66 | end 67 | 68 | # Print the 10 slowest examples and example groups at the 69 | # end of the spec run, to help surface which specs are running 70 | # particularly slow. 71 | config.profile_examples = 10 72 | 73 | # Run specs in random order to surface order dependencies. If you find an 74 | # order dependency and want to debug it, you can fix the order by providing 75 | # the seed, which is printed after each run. 76 | # --seed 1234 77 | config.order = :random 78 | 79 | # Seed global randomization in this process using the `--seed` CLI option. 80 | # Setting this allows you to use `--seed` to deterministically reproduce 81 | # test failures related to randomization by passing the same `--seed` value 82 | # as the one that triggered the failure. 83 | Kernel.srand config.seed 84 | =end 85 | end 86 | -------------------------------------------------------------------------------- /ember/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{partial "home-nav"}} 3 |
4 |
5 | {{partial "widgets/large-logo"}} 6 | {{partial "users/register-form"}} 7 |
8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
It's simple.
16 |

We send you friendly "How'd your day go?" emails.

17 |

You reply and write as much as you want.

18 |

You end up with a neat collection of your life stories.

19 |
20 |
21 |
It's private.
22 |

Thoughts, ideas, memories.

23 |

All saved in one place.

24 |

Never shared, only viewable by you.

25 |
26 |
27 |
Our pledge to you.
28 |

We'll never get acquired.

29 |

We'll never shut down.

30 |

You pay. We keep the lights on.

31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 |
Why Give it a Try?
39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 |
    50 |
  • 51 |

    OhLife Importer

    52 |

    53 | The perfect OhLife replacement, built 54 | to last. Easily import your OhLife entries. 55 |

    56 | 57 |
  • 58 |
  • 59 |

    Multiple Views

    60 |

    61 | Enjoy multiple, beautiful ways to browse your journal. 62 | Skip to random entries, view by month, and more. 63 |

    64 | 65 |
  • 66 |
  • 67 |

    Searchable

    68 |

    69 | Powerful search by term and/or month 70 | makes it easy to find that long lost memory. 71 |

    72 | 73 |
  • 74 |
  • 75 |

    Blasts From the Past

    76 |

    77 | Included in each reminder email is an old entry, 78 | to remind you of that memory from last year. 79 |

    80 | 81 |
  • 82 |
  • 83 |

    Exportable

    84 |

    85 | 1-Click export. At any time, we'll email you a text file 86 | with all of your entries. 87 |

    88 | 89 |
  • 90 |
91 |
92 |
93 |
94 |
95 | 96 |
97 | 98 |
99 | 100 |

Oh, and DayJot is open source. 101 |
102 | If $1 per month is too rich for you, you can run it yourself! 103 |

104 | 105 |


106 |
Sign up for DayJot for free
107 |
108 |
109 | 110 |
111 |
112 | Fork me on GitHub 113 |
114 |
115 | -------------------------------------------------------------------------------- /ember/app/templates/about.hbs: -------------------------------------------------------------------------------- 1 | {{view "site-nav"}} 2 | 3 |
4 | 5 |
6 |
7 |
Hi. Welcome to DayJot.
8 |
9 | 10 |
11 |

12 | Your private journal, for $1 a month. 13 |
14 | Our pledge to you. 15 |

16 |

1. We'll never get acquired.

17 |

2. We'll never shut down.

18 |

3. You pay. We keep the lights on.

19 |
20 | 21 |
22 |

What

23 |

24 | DayJot is your online, private journal, designed to last. 25 |

26 |

27 | Every day, we send you friendly "How'd your day go?" emails. 28 | You reply and write as much as you want. 29 | You end up with a neat collection of your life stories. 30 |

31 |

32 | It's permanent, private, and secure, as journals should be. 33 |

34 |

35 | Unlike other journal services, we ask that each member pay at least $1 per month to use the service. 36 | Your privacy is paramount. We don't sell your information, and we don't scan your 37 | entries to show you targeted ads. Your data is your data, all we do is 38 | store it for you. A journal service that respects your privacy cannot realistically make money, 39 | other than through direct payment from members. This is why we ask for at least $1. 40 |

41 |
42 | 43 |
44 |

Why

45 |

46 | Recently, a prominent free online journaling service, OhLife, shutdown. 47 | A more permanent online journaling solution is needed, so that 48 | this doesn't happen. 49 | That said, DayJot is actually inspired by OhLife, so thank you! 50 |

51 |
52 | 53 |
54 |

Who

55 |

56 | Who am I, you ask? My name is Marc MacLeod, and you can find me on twitter 57 | @marbemac. 58 |

59 | I've 60 | been building websites and apps for over a decade, and created DayJot to fill 61 | the need described above. I built it to run at minimal cost, and to be easy to 62 | extend and maintain. 63 |

64 |

65 | I use this product myself, and I'm 100% confident.. ok, 99.99% confident in the pledge 66 | above. If you have any questions or concerns feel free to reach out to me on Twitter, 67 | or at hi@dayjot.com. 68 |

69 |
70 | 71 |
72 |

Privacy

73 |

Privacy is very important at DayJot.

74 |

Your personal information will never be shared or sold.

75 |

All data sent to and from DayJot is encrypted and stored in a secure location.

76 |

The DayJot database itself is encrypted, so even if an attacker got access to it, they could not read the information.

77 |

No Facebook. No Twitter. No sharing. By design, DayJot is not a social website.

78 |

Export your data anytime.

79 |

To completely delete your entire account and all associated data, just email help@dayjot.

80 |
81 | 82 |
83 |

Open Source

84 |

85 | DayJot is open source software. If you're concerned about security, or just want to run the service on your own private server, I 86 | encourage you to check the code out at https://github.com/marbemac/dayjot. 87 |

88 |

89 | Contributions are, of course, welcome :). 90 |

91 | 92 | 93 | 94 |
95 | 96 |
97 | 98 |
99 |
100 | Fork me on GitHub 101 |
102 |
103 | 104 |
--------------------------------------------------------------------------------