diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10e39ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore Byebug command history file. +.byebug_history + +config/postal.yml +config/smtp.cert +config/smtp.key +config/lets_encrypt.pem +config/signing.key diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..0c1a20f --- /dev/null +++ b/Gemfile @@ -0,0 +1,44 @@ +source 'https://rubygems.org' +gem 'rails', '= 5.0.2' +gem 'mysql2', '>= 0.3.18', '< 0.5' +gem 'puma', '~> 3.0' +gem 'sass-rails', '~> 5.0' +gem 'uglifier', '>= 1.3.0' +gem 'coffee-rails', '~> 4.2' +gem 'jquery-rails' +gem 'turbolinks', '~> 5' +gem 'haml' +gem 'nifty-utils' +gem 'nilify_blanks' +gem 'kaminari' +gem 'bcrypt' +gem 'foreman' +gem 'hashie' +gem 'authie', :git => "git@codebasehq.com:atechmedia/libs/authie" +gem 'dynamic_form' +gem 'changey' +gem 'mail' +gem 'autoprefixer-rails' +gem 'bunny', '~> 2.5.1' +gem 'secure_headers' +gem 'chronic' +gem 'basic_ssl' +gem 'clockwork' +gem 'encrypto_signo' +gem 'epoll', :require => nil +gem 'mongo' +gem 'sentry-raven' +gem 'gelf' +gem 'moonrope', :git => 'https://github.com/adamcooke/moonrope', :branch => 'master' +gem 'jwt' +gem 'createsend', '~> 4.0' +gem 'acme-client', :git => 'https://github.com/unixcharles/acme-client' + +group :development, :test do + gem 'byebug' +end + +group :development do + gem 'web-console' + gem 'annotate' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..a0b73cf --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,257 @@ +GIT + remote: git@codebasehq.com:atechmedia/libs/authie + revision: 045adc2e54e3ec6ced99ad49356da6397dd62542 + specs: + authie (3.0.0) + +GIT + remote: https://github.com/adamcooke/moonrope + revision: fe5d5de38a35c36416f256a41902a6984e147fe5 + branch: master + specs: + moonrope (2.0.0) + deep_merge (~> 1.0) + json (~> 1.7) + rack (>= 1.4) + +GIT + remote: https://github.com/unixcharles/acme-client + revision: 9a80e9301acd3f17ebff6ceaf245e0c400d08519 + specs: + acme-client (0.5.0) + faraday (~> 0.9, >= 0.9.1) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.0.2) + actionpack (= 5.0.2) + nio4r (>= 1.2, < 3.0) + websocket-driver (~> 0.6.1) + actionmailer (5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.0.2) + actionview (= 5.0.2) + activesupport (= 5.0.2) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.0.2) + activesupport (= 5.0.2) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.0.2) + activesupport (= 5.0.2) + globalid (>= 0.3.6) + activemodel (5.0.2) + activesupport (= 5.0.2) + activerecord (5.0.2) + activemodel (= 5.0.2) + activesupport (= 5.0.2) + arel (~> 7.0) + activesupport (5.0.2) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + amq-protocol (2.0.1) + annotate (2.7.1) + activerecord (>= 3.2, < 6.0) + rake (>= 10.4, < 12.0) + arel (7.1.4) + autoprefixer-rails (6.5.0.2) + execjs + basic_ssl (1.0.3) + bcrypt (3.1.11) + bson (4.0.4) + builder (3.2.3) + bunny (2.5.1) + amq-protocol (>= 2.0.1) + byebug (9.0.6) + changey (1.0.0) + activerecord (>= 4.2, < 6) + chronic (0.10.2) + clockwork (1.0.0) + activesupport + tzinfo + coffee-rails (4.2.1) + coffee-script (>= 2.2.0) + railties (>= 4.0.0, < 5.2.x) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.10.0) + concurrent-ruby (1.0.5) + createsend (4.1.0) + hashie (~> 3.0) + httparty (~> 0.10) + json (~> 1.0) + debug_inspector (0.0.2) + deep_merge (1.1.1) + dynamic_form (1.1.4) + encrypto_signo (1.0.0) + epoll (0.3.0) + erubis (2.7.0) + execjs (2.7.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + foreman (0.82.0) + thor (~> 0.19.1) + gelf (3.0.0) + json + globalid (0.3.7) + activesupport (>= 4.1.0) + haml (4.0.7) + tilt + hashie (3.4.6) + httparty (0.14.0) + multi_xml (>= 0.5.2) + i18n (0.8.1) + jquery-rails (4.2.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + json (1.8.6) + jwt (1.5.6) + kaminari (0.17.0) + actionpack (>= 3.0.0) + activesupport (>= 3.0.0) + loofah (2.0.3) + nokogiri (>= 1.5.9) + mail (2.6.4) + mime-types (>= 1.16, < 4) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) + minitest (5.10.1) + mongo (2.2.4) + bson (~> 4.0) + multi_xml (0.5.5) + multipart-post (2.0.0) + mysql2 (0.4.4) + nifty-utils (1.1.7) + nilify_blanks (1.2.1) + activerecord (>= 3.0.0) + activesupport (>= 3.0.0) + nio4r (2.0.0) + nokogiri (1.7.1) + mini_portile2 (~> 2.1.0) + puma (3.8.2) + rack (2.0.1) + rack-test (0.6.3) + rack (>= 1.0) + rails (5.0.2) + actioncable (= 5.0.2) + actionmailer (= 5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) + activemodel (= 5.0.2) + activerecord (= 5.0.2) + activesupport (= 5.0.2) + bundler (>= 1.3.0, < 2.0) + railties (= 5.0.2) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.2) + activesupport (>= 4.2.0, < 6.0) + nokogiri (~> 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.0.2) + actionpack (= 5.0.2) + activesupport (= 5.0.2) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (11.3.0) + sass (3.4.22) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + secure_headers (3.4.1) + useragent + sentry-raven (2.1.0) + faraday (>= 0.7.6, < 0.10.x) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.5) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.0) + tzinfo (1.2.3) + thread_safe (~> 0.1) + uglifier (3.0.2) + execjs (>= 0.3.0, < 3) + useragent (0.16.8) + web-console (3.3.1) + actionview (>= 5.0) + activemodel (>= 5.0) + debug_inspector + railties (>= 5.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + +PLATFORMS + ruby + +DEPENDENCIES + acme-client! + annotate + authie! + autoprefixer-rails + basic_ssl + bcrypt + bunny (~> 2.5.1) + byebug + changey + chronic + clockwork + coffee-rails (~> 4.2) + createsend (~> 4.0) + dynamic_form + encrypto_signo + epoll + foreman + gelf + haml + hashie + jquery-rails + jwt + kaminari + mail + mongo + moonrope! + mysql2 (>= 0.3.18, < 0.5) + nifty-utils + nilify_blanks + puma (~> 3.0) + rails (= 5.0.2) + sass-rails (~> 5.0) + secure_headers + sentry-raven + turbolinks (~> 5) + uglifier (>= 1.3.0) + web-console + +BUNDLED WITH + 1.14.5 diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..7998818 --- /dev/null +++ b/Procfile @@ -0,0 +1,6 @@ +web: bundle exec puma -C config/puma.rb +fast: bundle exec rake postal:fast_server +worker: bundle exec rake postal:worker +cron: bundle exec rake postal:cron +smtp: bundle exec rake postal:smtp_server +requeuer: bundle exec rake postal:requeuer diff --git a/Procfile.options b/Procfile.options new file mode 100644 index 0000000..7262d7e --- /dev/null +++ b/Procfile.options @@ -0,0 +1,14 @@ +app_name: Postal +log_path: log/procodile.log +pid_root: tmp/pids +processes: + web: + restart_mode: usr1 + fast: + restart_mode: term-start + smtp: + restart_mode: usr1 + worker: + restart_mode: start-term + cron: + restart_mode: term-start diff --git a/README.md b/README.md new file mode 100644 index 0000000..61a6079 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Postal + +Postal is a complete and fully featured mail server for use by websites & web servers. Think Sendgrid, Mailgun or Postmark but open source and ready for you to run on your own servers. Postal was developed by aTech Media to serve its own mail processing requirements and we have since decided that it should be released as an open source project for the community. It was originally launched by us as AppMail but renamed to Postal as part of making it open source as we felt the name was more suitable. + +The application has been running in production for us for nearly 6 months and we will be continuing to use it ourselves and support its ongoing development. + +We are still just putting a few finishing touches to the documentation & installation instructions but we'll make it as easy as possible. + +Standby. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e85f913 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/api/authenticator.rb b/api/authenticator.rb new file mode 100644 index 0000000..7429b00 --- /dev/null +++ b/api/authenticator.rb @@ -0,0 +1,28 @@ +authenticator :server do + header "X-Server-API-Key", "The API token for a server that you wish to authenticate with.", :example => 'f29a45f0d4e1744ebaee' + error 'InvalidServerAPIKey', "The API token provided in X-Server-API-Key was not valid.", :attributes => {:token => "The token that was looked up"} + error 'ServerSuspended', "The mail server has been suspended" + lookup do + if key = request.headers['X-Server-API-Key'] + if credential = Credential.where(:type => 'API', :key => key).first + if credential.server.suspended? + error 'ServerSuspended' + else + credential.use + credential + end + else + error 'InvalidServerAPIKey', :token => key + end + end + end + rule :default, "AccessDenied", "Must be authenticated as a server." do + identity.is_a?(Credential) + end +end + +authenticator :anonymous do + rule :default, "MustNotBeAuthenticated", "Must not be authenticated." do + identity.nil? + end +end diff --git a/api/controllers/messages_api_controller.rb b/api/controllers/messages_api_controller.rb new file mode 100644 index 0000000..d24c7cd --- /dev/null +++ b/api/controllers/messages_api_controller.rb @@ -0,0 +1,40 @@ +controller :messages do + friendly_name "Messages API" + description "This API allows you to access message details" + authenticator :server + + action :message do + title "Return message details" + description "Returns all details about a message" + param :id, "The ID of the message", :type => Integer, :required => true + returns Hash, :structure => :message, :structure_opts => {:paramable => {:expansions => false}} + error 'MessageNotFound', "No message found matching provided ID", :attributes => {:id => "The ID of the message"} + action do + begin + message = identity.server.message(params.id) + rescue Postal::MessageDB::Message::NotFound => e + error 'MessageNotFound', :id => params.id + end + structure :message, message, :return => true + end + end + + action :deliveries do + title "Return deliveries for a message" + description "Returns an array of deliveries which have been attempted for this message" + param :id, "The ID of the message", :type => Integer, :required => true + returns Array, :structure => :delivery, :structure_opts => {:full => true} + error 'MessageNotFound', "No message found matching provided ID", :attributes => {:id => "The ID of the message"} + action do + begin + message = identity.server.message(params.id) + rescue Postal::MessageDB::Message::NotFound => e + error 'MessageNotFound', :id => params.id + end + message.deliveries.map do |d| + structure :delivery, d + end + end + end + +end diff --git a/api/controllers/send_api_controller.rb b/api/controllers/send_api_controller.rb new file mode 100644 index 0000000..de45849 --- /dev/null +++ b/api/controllers/send_api_controller.rb @@ -0,0 +1,109 @@ +controller :send do + friendly_name "Send API" + description "This API allows you to send messages" + authenticator :server + + action :message do + title "Send a message" + description "This action allows you to send a message by providing the appropriate options" + # Acceptable Parameters + param :to, "The e-mail addresses of the recipients (max 50)", :type => Array + param :cc, "The e-mail addresses of any CC contacts (max 50)", :type => Array + param :bcc, "The e-mail addresses of any BCC contacts (max 50)", :type => Array + param :from, "The e-mail address of the sender", :type => String + param :subject, "The subject of the e-mail", :type => String + param :tag, "The tag of the e-mail", :type => String + param :reply_to, "Set the reply-to address for the mail", :type => String + param :plain_body, "The plain text body of the e-mail", :type => String + param :html_body, "The HTML body of the e-mail", :type => String + param :attachments, "An array of attachments for this e-mail", :type => Array + param :headers, "A hash of additional headers", :type => Hash + param :bounce, "Is this message a bounce?", :type => :boolean + # Errors + error 'ValidationError', "The provided data was not sufficient to send an email", :attributes => {:errors => "A hash of error details"} + error 'NoRecipients', "There are no recipients defined to received this message" + error 'NoContent', "There is no content defined for this e-mail" + error 'TooManyToAddresses', "The maximum number of To addresses has been reached (maximum 50)" + error 'TooManyCCAddresses', "The maximum number of CC addresses has been reached (maximum 50)" + error 'TooManyBCCAddresses', "The maximum number of BCC addresses has been reached (maximum 50)" + error 'FromAddressMissing', "The From address is missing and is required" + error 'UnauthenticatedFromAddress', "The From address is not authorised to send mail from this server" + error 'AttachmentMissingName', "An attachment is missing a name" + error 'AttachmentMissingData', "An attachment is missing data" + # Return + returns Hash + # Action + action do + attributes = {} + attributes[:to] = params.to + attributes[:cc] = params.cc + attributes[:bcc] = params.bcc + attributes[:from] = params.from + attributes[:sender] = params.sender + attributes[:subject] = params.subject + attributes[:reply_to] = params.reply_to + attributes[:plain_body] = params.plain_body + attributes[:html_body] = params.html_body + attributes[:bounce] = params.bounce ? true : false + attributes[:tag] = params.tag + attributes[:custom_headers] = params.headers + attributes[:attachments] = [] + (params.attachments || []).each do |attachment| + next unless attachment.is_a?(Hash) + attributes[:attachments] << {:name => attachment['name'], :content_type => attachment['content_type'], :data => attachment['data'], :base64 => true} + end + message = OutgoingMessagePrototype.new(identity.server, request.ip, 'api', attributes) + message.credential = identity + if message.valid? + result = message.create_messages + {:message_id => message.message_id, :messages => result} + else + error message.errors.first + end + end + end + + action :raw do + title "Send a raw RFC2882 message" + description "This action allows you to send us a raw RFC2822 formatted message along with the recipients that it should be sent to. This is similar to sending a message through our SMTP service." + param :mail_from, "The address that should be logged as sending the message", :type => String, :required => true + param :rcpt_to, "The addresses this message should be sent to", :type => Array, :required => true + param :data, "A base64 encoded RFC2822 message to send", :type => String, :required => true + param :bounce, "Is this message a bounce?", :type => :boolean + returns Hash + error 'UnauthenticatedFromAddress', "The From address is not authorised to send mail from this server" + action do + # Decode the raw message + raw_message = Base64.decode64(params.data) + + # Parse through mail to get the from/sender headers + mail = Mail.new(raw_message.split("\r\n\r\n", 2).first) + from_headers = {'from' => mail.from, 'sender' => mail.sender} + authenticated_domain = identity.server.find_authenticated_domain_from_headers(from_headers) + + # If we're not authenticated, don't continue + if authenticated_domain.nil? + error 'UnauthenticatedFromAddress' + end + + # Store the result ready to return + result = {:message_id => nil, :messages => {}} + params.rcpt_to.uniq.each do |rcpt_to| + message = identity.server.message_db.new_message + message.rcpt_to = rcpt_to + message.mail_from = params.mail_from + message.raw_message = raw_message + message.received_with_ssl = true + message.scope = 'outgoing' + message.domain_id = authenticated_domain.id + message.credential_id = identity.id + message.bounce = params.bounce ? 1 : 0 + message.save + result[:message_id] = message.message_id if result[:message_id].nil? + result[:messages][rcpt_to] = {:id => message.id, :token => message.token} + end + result + end + end + +end diff --git a/api/structures/delivery_api_structure.rb b/api/structures/delivery_api_structure.rb new file mode 100644 index 0000000..1f845a9 --- /dev/null +++ b/api/structures/delivery_api_structure.rb @@ -0,0 +1,10 @@ +structure :delivery do + basic :id + basic :status + basic :details + basic :output, :value => proc { o.output&.strip } + basic :sent_with_ssl, :value => proc { o.sent_with_ssl == 1 } + basic :log_id + basic :time, :value => proc { o.time&.to_f } + basic :timestamp, :value => proc { o.timestamp.to_f } +end diff --git a/api/structures/message_api_structure.rb b/api/structures/message_api_structure.rb new file mode 100644 index 0000000..5e34ff1 --- /dev/null +++ b/api/structures/message_api_structure.rb @@ -0,0 +1,59 @@ +structure :message do + basic :id + basic :token + + expansion(:status) { + { + :status => o.status, + :last_delivery_attempt => o.last_delivery_attempt ? o.last_delivery_attempt.to_f : nil, + :held => o.held == 1 ? true : false, + :hold_expiry => o.hold_expiry ? o.hold_expiry.to_f : nil + } + } + + expansion(:details) { + { + :rcpt_to => o.rcpt_to, + :mail_from => o.mail_from, + :subject => o.subject, + :message_id => o.message_id, + :timestamp => o.timestamp.to_f, + :direction => o.scope, + :size => o.size, + :bounce => o.bounce, + :bounce_for_id => o.bounce_for_id, + :tag => o.tag, + :received_with_ssl => o.received_with_ssl + } + } + + expansion(:inspection) { + { + :inspected => o.inspected == 1 ? true : false, + :spam => o.spam == 1 ? true : false, + :spam_score => o.spam_score.to_f, + :threat => o.threat == 1 ? true : false, + :threat_details => o.threat_details + } + } + + expansion(:plain_body) { o.plain_body } + + expansion(:html_body) { o.html_body } + + expansion(:attachments) { + o.attachments.map do |attachment| + { + :filename => attachment.filename.to_s, + :content_type => attachment.mime_type, + :data => Base64.encode64(attachment.body.to_s), + :size => attachment.body.to_s.bytesize, + :hash => Digest::SHA1.hexdigest(attachment.body.to_s) + } + end + } + + expansion(:headers) { o.headers } + + expansion(:raw_message) { Base64.encode64(o.raw_message) } +end diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..b16e53d --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/fonts/DroidSansMono.eot b/app/assets/fonts/DroidSansMono.eot new file mode 100644 index 0000000..7c2a63f Binary files /dev/null and b/app/assets/fonts/DroidSansMono.eot differ diff --git a/app/assets/fonts/DroidSansMono.ttf b/app/assets/fonts/DroidSansMono.ttf new file mode 100644 index 0000000..6022784 Binary files /dev/null and b/app/assets/fonts/DroidSansMono.ttf differ diff --git a/app/assets/fonts/DroidSansMono.woff b/app/assets/fonts/DroidSansMono.woff new file mode 100644 index 0000000..241015b Binary files /dev/null and b/app/assets/fonts/DroidSansMono.woff differ diff --git a/app/assets/fonts/SourceSansPro-Black.eot b/app/assets/fonts/SourceSansPro-Black.eot new file mode 100644 index 0000000..3d44cc2 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Black.eot differ diff --git a/app/assets/fonts/SourceSansPro-Black.ttf b/app/assets/fonts/SourceSansPro-Black.ttf new file mode 100644 index 0000000..479b40a Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Black.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Black.woff b/app/assets/fonts/SourceSansPro-Black.woff new file mode 100644 index 0000000..6906484 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Black.woff differ diff --git a/app/assets/fonts/SourceSansPro-Bold.eot b/app/assets/fonts/SourceSansPro-Bold.eot new file mode 100644 index 0000000..1625d73 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Bold.eot differ diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf b/app/assets/fonts/SourceSansPro-Bold.ttf new file mode 100644 index 0000000..07fae00 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Bold.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Bold.woff b/app/assets/fonts/SourceSansPro-Bold.woff new file mode 100644 index 0000000..a186b83 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Bold.woff differ diff --git a/app/assets/fonts/SourceSansPro-Light.eot b/app/assets/fonts/SourceSansPro-Light.eot new file mode 100644 index 0000000..41d87ba Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Light.eot differ diff --git a/app/assets/fonts/SourceSansPro-Light.ttf b/app/assets/fonts/SourceSansPro-Light.ttf new file mode 100644 index 0000000..032fecf Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Light.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Light.woff b/app/assets/fonts/SourceSansPro-Light.woff new file mode 100644 index 0000000..555a118 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Light.woff differ diff --git a/app/assets/fonts/SourceSansPro-Regular.eot b/app/assets/fonts/SourceSansPro-Regular.eot new file mode 100644 index 0000000..b11724a Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Regular.eot differ diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf b/app/assets/fonts/SourceSansPro-Regular.ttf new file mode 100644 index 0000000..4709466 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Regular.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Regular.woff b/app/assets/fonts/SourceSansPro-Regular.woff new file mode 100644 index 0000000..7182e72 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Regular.woff differ diff --git a/app/assets/fonts/SourceSansPro-Semibold.eot b/app/assets/fonts/SourceSansPro-Semibold.eot new file mode 100644 index 0000000..54cafa9 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Semibold.eot differ diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf b/app/assets/fonts/SourceSansPro-Semibold.ttf new file mode 100644 index 0000000..4d7fedf Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Semibold.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Semibold.woff b/app/assets/fonts/SourceSansPro-Semibold.woff new file mode 100644 index 0000000..373005e Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Semibold.woff differ diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/images/animals/cat.svg b/app/assets/images/animals/cat.svg new file mode 100644 index 0000000..1ea0ad3 --- /dev/null +++ b/app/assets/images/animals/cat.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/cat2.svg b/app/assets/images/animals/cat2.svg new file mode 100644 index 0000000..739e1e1 --- /dev/null +++ b/app/assets/images/animals/cat2.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/cat3.svg b/app/assets/images/animals/cat3.svg new file mode 100644 index 0000000..1c3f5c9 --- /dev/null +++ b/app/assets/images/animals/cat3.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/cat4.svg b/app/assets/images/animals/cat4.svg new file mode 100644 index 0000000..da1305c --- /dev/null +++ b/app/assets/images/animals/cat4.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/cock.svg b/app/assets/images/animals/cock.svg new file mode 100644 index 0000000..a53e63c --- /dev/null +++ b/app/assets/images/animals/cock.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/deer.svg b/app/assets/images/animals/deer.svg new file mode 100644 index 0000000..7d19c29 --- /dev/null +++ b/app/assets/images/animals/deer.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/dog.svg b/app/assets/images/animals/dog.svg new file mode 100644 index 0000000..79d71bb --- /dev/null +++ b/app/assets/images/animals/dog.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/fox-white.svg b/app/assets/images/animals/fox-white.svg new file mode 100644 index 0000000..e1d2af4 --- /dev/null +++ b/app/assets/images/animals/fox-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/fox.svg b/app/assets/images/animals/fox.svg new file mode 100644 index 0000000..a87a1a6 --- /dev/null +++ b/app/assets/images/animals/fox.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/goat.svg b/app/assets/images/animals/goat.svg new file mode 100644 index 0000000..985a3aa --- /dev/null +++ b/app/assets/images/animals/goat.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/koala.svg b/app/assets/images/animals/koala.svg new file mode 100644 index 0000000..b6c9cb2 --- /dev/null +++ b/app/assets/images/animals/koala.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/lion.svg b/app/assets/images/animals/lion.svg new file mode 100644 index 0000000..aae894b --- /dev/null +++ b/app/assets/images/animals/lion.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/monkey.svg b/app/assets/images/animals/monkey.svg new file mode 100644 index 0000000..56a6f9f --- /dev/null +++ b/app/assets/images/animals/monkey.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/owl.svg b/app/assets/images/animals/owl.svg new file mode 100644 index 0000000..649a2b6 --- /dev/null +++ b/app/assets/images/animals/owl.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/panda.svg b/app/assets/images/animals/panda.svg new file mode 100644 index 0000000..debb550 --- /dev/null +++ b/app/assets/images/animals/panda.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/penguin.svg b/app/assets/images/animals/penguin.svg new file mode 100644 index 0000000..60ca46d --- /dev/null +++ b/app/assets/images/animals/penguin.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/animals/wolf.svg b/app/assets/images/animals/wolf.svg new file mode 100644 index 0000000..74b34fd --- /dev/null +++ b/app/assets/images/animals/wolf.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/button-spinner-danger.gif b/app/assets/images/button-spinner-danger.gif new file mode 100644 index 0000000..cd99d28 Binary files /dev/null and b/app/assets/images/button-spinner-danger.gif differ diff --git a/app/assets/images/button-spinner-dark.gif b/app/assets/images/button-spinner-dark.gif new file mode 100644 index 0000000..c1b1009 Binary files /dev/null and b/app/assets/images/button-spinner-dark.gif differ diff --git a/app/assets/images/button-spinner-neutral.gif b/app/assets/images/button-spinner-neutral.gif new file mode 100644 index 0000000..23a0d7d Binary files /dev/null and b/app/assets/images/button-spinner-neutral.gif differ diff --git a/app/assets/images/button-spinner-positive.gif b/app/assets/images/button-spinner-positive.gif new file mode 100644 index 0000000..17c6136 Binary files /dev/null and b/app/assets/images/button-spinner-positive.gif differ diff --git a/app/assets/images/button-spinner.gif b/app/assets/images/button-spinner.gif new file mode 100644 index 0000000..3ba47b1 Binary files /dev/null and b/app/assets/images/button-spinner.gif differ diff --git a/app/assets/images/cards/amex.png b/app/assets/images/cards/amex.png new file mode 100755 index 0000000..deb9433 Binary files /dev/null and b/app/assets/images/cards/amex.png differ diff --git a/app/assets/images/cards/mastercard.png b/app/assets/images/cards/mastercard.png new file mode 100755 index 0000000..f778f2d Binary files /dev/null and b/app/assets/images/cards/mastercard.png differ diff --git a/app/assets/images/cards/visa.png b/app/assets/images/cards/visa.png new file mode 100755 index 0000000..f816fef Binary files /dev/null and b/app/assets/images/cards/visa.png differ diff --git a/app/assets/images/favicon.png b/app/assets/images/favicon.png new file mode 100644 index 0000000..d726986 Binary files /dev/null and b/app/assets/images/favicon.png differ diff --git a/app/assets/images/icons/bats-white.svg b/app/assets/images/icons/bats-white.svg new file mode 100644 index 0000000..ef10cc2 --- /dev/null +++ b/app/assets/images/icons/bats-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/box-white.svg b/app/assets/images/icons/box-white.svg new file mode 100644 index 0000000..524bfff --- /dev/null +++ b/app/assets/images/icons/box-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/conveyor.svg b/app/assets/images/icons/conveyor.svg new file mode 100644 index 0000000..b443bc1 --- /dev/null +++ b/app/assets/images/icons/conveyor.svg @@ -0,0 +1,15 @@ + + + + zf6 + Created with Sketch. + + + + + + + + + + diff --git a/app/assets/images/icons/cross-grey.svg b/app/assets/images/icons/cross-grey.svg new file mode 100644 index 0000000..e3f6063 --- /dev/null +++ b/app/assets/images/icons/cross-grey.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/cross-orange.svg b/app/assets/images/icons/cross-orange.svg new file mode 100644 index 0000000..34ff181 --- /dev/null +++ b/app/assets/images/icons/cross-orange.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/drop-arrow-white.svg b/app/assets/images/icons/drop-arrow-white.svg new file mode 100644 index 0000000..15897ce --- /dev/null +++ b/app/assets/images/icons/drop-arrow-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/email.svg b/app/assets/images/icons/email.svg new file mode 100644 index 0000000..ad021a6 --- /dev/null +++ b/app/assets/images/icons/email.svg @@ -0,0 +1,15 @@ + + + + zf47 + Created with Sketch. + + + + + + + + + + diff --git a/app/assets/images/icons/eye.svg b/app/assets/images/icons/eye.svg new file mode 100644 index 0000000..3d5c003 --- /dev/null +++ b/app/assets/images/icons/eye.svg @@ -0,0 +1,13 @@ + + + + g11 + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/icons/help.svg b/app/assets/images/icons/help.svg new file mode 100644 index 0000000..1591662 --- /dev/null +++ b/app/assets/images/icons/help.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/incoming-mail-white.svg b/app/assets/images/icons/incoming-mail-white.svg new file mode 100644 index 0000000..4ef9035 --- /dev/null +++ b/app/assets/images/icons/incoming-mail-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/incoming-mail.svg b/app/assets/images/icons/incoming-mail.svg new file mode 100644 index 0000000..074f943 --- /dev/null +++ b/app/assets/images/icons/incoming-mail.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/lock.svg b/app/assets/images/icons/lock.svg new file mode 100644 index 0000000..96a3fa8 --- /dev/null +++ b/app/assets/images/icons/lock.svg @@ -0,0 +1,12 @@ + + + + g95 + Created with Sketch. + + + + + + + diff --git a/app/assets/images/icons/mouse.svg b/app/assets/images/icons/mouse.svg new file mode 100644 index 0000000..561a2de --- /dev/null +++ b/app/assets/images/icons/mouse.svg @@ -0,0 +1,14 @@ + + + + w50 + Created with Sketch. + + + + + + + + + diff --git a/app/assets/images/icons/organization-white.svg b/app/assets/images/icons/organization-white.svg new file mode 100644 index 0000000..5c0f2ae --- /dev/null +++ b/app/assets/images/icons/organization-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/outgoing-mail-white.svg b/app/assets/images/icons/outgoing-mail-white.svg new file mode 100644 index 0000000..31abbb0 --- /dev/null +++ b/app/assets/images/icons/outgoing-mail-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/outgoing-mail.svg b/app/assets/images/icons/outgoing-mail.svg new file mode 100644 index 0000000..8f6623a --- /dev/null +++ b/app/assets/images/icons/outgoing-mail.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/pause-white.svg b/app/assets/images/icons/pause-white.svg new file mode 100644 index 0000000..72e2c9f --- /dev/null +++ b/app/assets/images/icons/pause-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/premium.svg b/app/assets/images/icons/premium.svg new file mode 100644 index 0000000..a99f286 --- /dev/null +++ b/app/assets/images/icons/premium.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/return-path.svg b/app/assets/images/icons/return-path.svg new file mode 100644 index 0000000..9283b13 --- /dev/null +++ b/app/assets/images/icons/return-path.svg @@ -0,0 +1,12 @@ + + + + a195 + Created with Sketch. + + + + + + + diff --git a/app/assets/images/icons/search.svg b/app/assets/images/icons/search.svg new file mode 100644 index 0000000..fa80fba --- /dev/null +++ b/app/assets/images/icons/search.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/select-arrow.svg b/app/assets/images/icons/select-arrow.svg new file mode 100644 index 0000000..5b59e1a --- /dev/null +++ b/app/assets/images/icons/select-arrow.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/assets/images/icons/size-white.svg b/app/assets/images/icons/size-white.svg new file mode 100644 index 0000000..9a6f759 --- /dev/null +++ b/app/assets/images/icons/size-white.svg @@ -0,0 +1,16 @@ + + + + g120 + Created with Sketch. + + + + + + + + + + + diff --git a/app/assets/images/icons/tick-green.svg b/app/assets/images/icons/tick-green.svg new file mode 100644 index 0000000..86e91a4 --- /dev/null +++ b/app/assets/images/icons/tick-green.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/tick-grey.svg b/app/assets/images/icons/tick-grey.svg new file mode 100644 index 0000000..85037d7 --- /dev/null +++ b/app/assets/images/icons/tick-grey.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/trash-white.svg b/app/assets/images/icons/trash-white.svg new file mode 100644 index 0000000..3bf031d --- /dev/null +++ b/app/assets/images/icons/trash-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/truck.svg b/app/assets/images/icons/truck.svg new file mode 100644 index 0000000..42e30aa --- /dev/null +++ b/app/assets/images/icons/truck.svg @@ -0,0 +1,14 @@ + + + + zf30 + Created with Sketch. + + + + + + + + + diff --git a/app/assets/images/icons/user-white.svg b/app/assets/images/icons/user-white.svg new file mode 100644 index 0000000..a5cf92e --- /dev/null +++ b/app/assets/images/icons/user-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/icons/web.svg b/app/assets/images/icons/web.svg new file mode 100644 index 0000000..9bfcab1 --- /dev/null +++ b/app/assets/images/icons/web.svg @@ -0,0 +1,27 @@ + + + + g227 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/logo-grey.svg b/app/assets/images/logo-grey.svg new file mode 100644 index 0000000..be386b7 --- /dev/null +++ b/app/assets/images/logo-grey.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg new file mode 100644 index 0000000..a950aed --- /dev/null +++ b/app/assets/images/logo.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/spinner-sub.gif b/app/assets/images/spinner-sub.gif new file mode 100644 index 0000000..708fd5c Binary files /dev/null and b/app/assets/images/spinner-sub.gif differ diff --git a/app/assets/images/starter_pack.png b/app/assets/images/starter_pack.png new file mode 100644 index 0000000..7b59d97 Binary files /dev/null and b/app/assets/images/starter_pack.png differ diff --git a/app/assets/images/tracking_pixel.png b/app/assets/images/tracking_pixel.png new file mode 100644 index 0000000..c2b862b Binary files /dev/null and b/app/assets/images/tracking_pixel.png differ diff --git a/app/assets/javascripts/application/application.coffee b/app/assets/javascripts/application/application.coffee new file mode 100644 index 0000000..c0ea875 --- /dev/null +++ b/app/assets/javascripts/application/application.coffee @@ -0,0 +1,55 @@ +#= require jquery +#= require jquery_ujs +#= require turbolinks +#= require_tree ./vendor/. +#= require_self +#= require_tree . + +$ -> + + isFirefox = -> !!navigator.userAgent.match(/firefox/i) + + $('html').addClass('browser-firefox') if isFirefox() + + $(document).on 'turbolinks:load', -> + $('.js-multibox').multibox({inputCount: 6, classNames: {container: "multibox", input: 'input input--text multibox__input'}}) + + $(document).on 'keyup', (event)-> + return if $(event.target).is('input, select, textarea') + if event.keyCode == 83 + $('.js-focus-on-s').focus() + event.preventDefault() + if event.keyCode == 70 + $('.js-focus-on-f').focus() + event.preventDefault() + + $(document).on 'click', 'html.main .flashMessage', -> + $(this).hide 'fast', -> + $(this).remove() + + $(document).on 'click', '.js-toggle-helpbox', -> + helpBox = $('.js-helpbox') + if helpBox.hasClass('is-hidden') + helpBox.removeClass('is-hidden') + else + helpBox.addClass('is-hidden') + return false + + $(document).on 'input', 'input[type=range]', -> + value = $(this).val() + updateAttr = $(this).attr('data-update') + if updateAttr && updateAttr.length + $("." + $(this).attr('data-update')).text(parseFloat(value, 10).toFixed(1)) + + $(document).on 'change', '.js-checkbox-list-toggle', -> + $this = $(this) + value = $this.val() + $list = $this.parent().find('.checkboxList') + if value == 'false' then $list.show() else $list.hide() + + $(document).on 'click', '.js-toggle', -> + $link = $(this) + element = $link.attr('data-element') + $(element, $link.parent()).toggle() + false + diff --git a/app/assets/javascripts/application/elements/ajax.coffee b/app/assets/javascripts/application/elements/ajax.coffee new file mode 100644 index 0000000..d0a3e0c --- /dev/null +++ b/app/assets/javascripts/application/elements/ajax.coffee @@ -0,0 +1,61 @@ +onStart = (event) -> + $('.flashMessage').remove() + $('input, select, textarea').blur() + $target = $(event.target) + if $target.is('form') + $('.js-form-submit', $target).addClass('is-spinning') + if $target.hasClass('button') + $($target).addClass('is-spinning') + +onComplete = (event, xhr)-> + $target = $(event.target) + if xhr.responseJSON + data = xhr.responseJSON + if data.redirect_to + Turbolinks.clearCache() + Turbolinks.visit(data.redirect_to, {"action":"replace"}) + console.log "Redirected to #{data.redirect_to}" + + if data.alert + unSpin($target) + alert(data.alert) + + if data.form_errors + if $target.is('form') + unSpin($target) + handleErrors($target, data.form_errors) + + if data.flash + unSpin($target) + $('body .flashMessage').remove() + for key, value of data.flash + $message = $("
#{value}
") + $('body').prepend($message) + + if data.region_html + unSpin($target) + $('.js-ajax-region').replaceWith(data.region_html) + $('[autofocus]', '.js-ajax-region').focus() + + else + console.log "Unsupported return." + +unSpin = ($target)-> + $target.removeClass('is-spinning') + $('.js-form-submit', $target).removeClass('is-spinning') + + +handleErrors = (form, errors)-> + html = $("