po_box 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +103 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/po_box_manifest.js +1 -0
  6. data/app/assets/stylesheets/po_box/application.css +15 -0
  7. data/app/controllers/po_box/application_controller.rb +4 -0
  8. data/app/controllers/po_box/emails_controller.rb +68 -0
  9. data/app/controllers/po_box/inboxes_controller.rb +54 -0
  10. data/app/helpers/po_box/application_helper.rb +4 -0
  11. data/app/helpers/po_box/emails_helper.rb +4 -0
  12. data/app/jobs/po_box/application_job.rb +4 -0
  13. data/app/mailboxes/application_mailbox.rb +2 -0
  14. data/app/mailboxes/po_box/inbox_mailbox.rb +54 -0
  15. data/app/mailers/po_box/application_mailer.rb +6 -0
  16. data/app/mailers/po_box/emailable_mailer.rb +12 -0
  17. data/app/models/concerns/po_box/emailable.rb +33 -0
  18. data/app/models/po_box/application_record.rb +5 -0
  19. data/app/models/po_box/email.rb +11 -0
  20. data/app/models/po_box/inbox.rb +11 -0
  21. data/app/views/layouts/po_box/application.html.erb +12 -0
  22. data/app/views/layouts/po_box/mailer.html.erb +12 -0
  23. data/app/views/layouts/po_box/mailer.text.erb +1 -0
  24. data/app/views/po_box/emails/_email.html.erb +49 -0
  25. data/app/views/po_box/emails/_form.html.erb +51 -0
  26. data/app/views/po_box/emails/edit.html.erb +7 -0
  27. data/app/views/po_box/emails/index.html.erb +11 -0
  28. data/app/views/po_box/emails/new.html.erb +6 -0
  29. data/app/views/po_box/emails/show.html.erb +7 -0
  30. data/app/views/po_box/home/index.html.erb +2 -0
  31. data/app/views/po_box/inboxes/_form.html.erb +19 -0
  32. data/app/views/po_box/inboxes/_inbox.html.erb +6 -0
  33. data/app/views/po_box/inboxes/edit.html.erb +7 -0
  34. data/app/views/po_box/inboxes/index.html.erb +15 -0
  35. data/app/views/po_box/inboxes/new.html.erb +6 -0
  36. data/app/views/po_box/inboxes/show.html.erb +8 -0
  37. data/config/initializers/po_box.rb +1 -0
  38. data/config/routes.rb +7 -0
  39. data/db/migrate/20230425172819_create_po_box_inboxes.rb +10 -0
  40. data/db/migrate/20230426005820_create_po_box_emails.rb +17 -0
  41. data/lib/generators/po_box/USAGE +12 -0
  42. data/lib/generators/po_box/po_box_generator.rb +33 -0
  43. data/lib/generators/po_box/templates/po_box.rb +1 -0
  44. data/lib/po_box/engine.rb +13 -0
  45. data/lib/po_box/version.rb +3 -0
  46. data/lib/po_box.rb +10 -0
  47. data/lib/tasks/po_box.rake +8 -0
  48. metadata +232 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4a96717f6fc9f8aee68cbf574b60c2da573d7693a1173279a84b16e1a1666d38
4
+ data.tar.gz: b5b7dcb9dcea500041f806517edc8ece9cdc18af18241900ce28adc43dd525de
5
+ SHA512:
6
+ metadata.gz: 5522631b60e572e57049274c7c730d257c44eb94d0ef900b528c628329dfd0ac68e0ea166be00c8773ae3c9d3c46693c6336971881205553228760259a637b9e
7
+ data.tar.gz: d6209dc03e50daa7ff937fee1ae5b39075a4a54b7215a7e80ec9290b9502864f5e1fc341e0a3585eacdfa7d9baa7de34ee8c644032a4627c337ff649ed96fede
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Leo Policastro
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # PoBox
2
+
3
+ PoBox is a Ruby on Rails engine that provides inboxes and email addresses for your users.
4
+
5
+ ## Requirements
6
+
7
+ 1. [ActiveStorage](https://edgeguides.rubyonrails.org/active_storage_overview.html) needs to be installed and configured.
8
+ 2. [ActionMailbox](https://edgeguides.rubyonrails.org/action_mailbox_basics.html) needs to be installed and configured.
9
+ 3. A model that the inbox and emails will be associated with needs to exist in the host application. e.g. `User` or `Account`.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem "po_box"
17
+ ```
18
+
19
+ And then run:
20
+
21
+ ```bash
22
+ $ bundle
23
+ ```
24
+
25
+ Or install it yourself as:
26
+
27
+ ```bash
28
+ $ gem install po_box
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ **IMPORTANT:** Make sure you have ActiveStorage and ActionMailbox installed and configured.
34
+
35
+ 1. `bin/rails po_box:install:migrations`
36
+ 2. `bin/rails g po_box emailable_model` (where `emailable_model` is the name of the model you want to make emailable, e.g. `user`)
37
+ 3. `bin/rails db:migrate`
38
+ 4. `bin/rails po_box:backfill_emailables`
39
+
40
+ ## Limitations
41
+
42
+ 1. Can only be applied to one model per application, for now.
43
+
44
+ ## Usage
45
+
46
+ Visit http://localhost:3000/rails/conductor/action_mailbox/inbound_emails to send an email to your app.
47
+
48
+ Send the email to the address that is associated with the emailable model. e.g.
49
+
50
+ ```ruby
51
+ User.inboxes.first.address
52
+
53
+ # => "some#64b0@yourapp"
54
+ ```
55
+
56
+ <!-- If your emailable_class is a User -->
57
+
58
+ ```ruby
59
+
60
+ irb(main):003:0> u = User.last
61
+ User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
62
+ =>
63
+ #<User:0x000000011131a328
64
+ ...
65
+ irb(main):004:0> u.inboxes.first
66
+ PoBox::Inbox Load (0.4ms) SELECT "po_box_inboxes".* FROM "po_box_inboxes" WHERE "po_box_inboxes"."emailable_id" = $1 AND "po_box_inboxes"."emailable_type" = $2 ORDER BY "po_box_inboxes"."id" ASC LIMIT $3 [["emailable_id", 1], ["emailable_type", "User"], ["LIMIT", 1]]
67
+ =>
68
+ #<PoBox::Inbox:0x00000001113119a8
69
+ id: 1,
70
+ emailable_type: "User",
71
+ emailable_id: 1,
72
+ address: "some#64b0",
73
+ created_at: Thu, 04 May 2023 00:28:13.107181000 UTC +00:00,
74
+ updated_at: Thu, 04 May 2023 00:28:13.107181000 UTC +00:00>
75
+ irb(main):005:0>
76
+
77
+ irb(main):005:0> u.inboxes.first.emails.last
78
+ PoBox::Inbox Load (0.5ms) SELECT "po_box_inboxes".* FROM "po_box_inboxes" WHERE "po_box_inboxes"."emailable_id" = $1 AND "po_box_inboxes"."emailable_type" = $2 ORDER BY "po_box_inboxes"."id" ASC LIMIT $3 [["emailable_id", 1], ["emailable_type", "User"], ["LIMIT", 1]]
79
+ PoBox::Email Load (0.5ms) SELECT "po_box_emails".* FROM "po_box_emails" WHERE "po_box_emails"."inbox_id" = $1 ORDER BY "po_box_emails"."id" DESC LIMIT $2 [["inbox_id", 1], ["LIMIT", 1]]
80
+ =>
81
+ #<PoBox::Email:0x00000001079c27e8
82
+ id: 1,
83
+ inbox_id: 1,
84
+ cc: nil,
85
+ from: "anyone@aol.com",
86
+ multipart: true,
87
+ read: false,
88
+ subject: "Something subjecty",
89
+ to: "some#64b0@yourapp.com",
90
+ message_id: "6452fc3aa9815_568e39a83101b@My-MacBook-Pro-2.local.mail",
91
+ body: "Hello World",
92
+ created_at: Thu, 04 May 2023 00:28:44.171561000 UTC +00:00,
93
+ updated_at: Thu, 04 May 2023 00:28:44.483199000 UTC +00:00>
94
+ irb(main):006:0>
95
+ ```
96
+
97
+ ## Contributing
98
+
99
+ Bug reports and pull requests are always welcome!
100
+
101
+ ## License
102
+
103
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/po_box .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module PoBox
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,68 @@
1
+ module PoBox
2
+ class EmailsController < ApplicationController
3
+ before_action :set_inbox
4
+ before_action :set_email, only: %i[show edit update destroy]
5
+
6
+ # GET /emails
7
+ def index
8
+ @emails = Email.all
9
+ end
10
+
11
+ # GET /emails/1
12
+ def show
13
+ end
14
+
15
+ # GET /emails/new
16
+ def new
17
+ @email = Email.new
18
+ end
19
+
20
+ # GET /emails/1/edit
21
+ def edit
22
+ end
23
+
24
+ # POST /emails
25
+ def create
26
+ @email = Email.new(email_params)
27
+
28
+ if @email.save
29
+ redirect_to inbox_email_path(@inbox, @email), notice: "Email was successfully created."
30
+ else
31
+ render :new, status: :unprocessable_entity
32
+ end
33
+ end
34
+
35
+ # PATCH/PUT /emails/1
36
+ def update
37
+ if @email.update(email_params)
38
+ redirect_to inbox_email_path(@inbox, @email), notice: "Email was successfully updated."
39
+ else
40
+ render :edit, status: :unprocessable_entity
41
+ end
42
+ end
43
+
44
+ # DELETE /emails/1
45
+ def destroy
46
+ @email.destroy
47
+ redirect_to inbox_emails_url(@inbox), notice: "Email was successfully destroyed."
48
+ end
49
+
50
+ private
51
+
52
+ def set_inbox
53
+ @inbox = Inbox.find(params[:inbox_id])
54
+ rescue ActiveRecord::RecordNotFound
55
+ redirect_to po_box_inboxes_path, notice: "Inbox not found"
56
+ end
57
+
58
+ # Use callbacks to share common setup or constraints between actions.
59
+ def set_email
60
+ @email = Email.find(params[:id])
61
+ end
62
+
63
+ # Only allow a list of trusted parameters through.
64
+ def email_params
65
+ params.require(:email).permit(:inbox_id, :cc, :from, :multipart, :read, :subject, :to, :message_id)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,54 @@
1
+ module PoBox
2
+ class InboxesController < ApplicationController
3
+ before_action :set_inbox, only: %i[show edit update destroy]
4
+
5
+ def index
6
+ @inboxes = Inbox.all
7
+ end
8
+
9
+ def show
10
+ end
11
+
12
+ def new
13
+ @inbox = Inbox.new
14
+ end
15
+
16
+ def edit
17
+ end
18
+
19
+ def create
20
+ @inbox = Inbox.new(inbox_params)
21
+
22
+ if @inbox.save
23
+ redirect_to @inbox, notice: "Inbox was successfully created."
24
+ else
25
+ render :new, status: :unprocessable_entity
26
+ end
27
+ end
28
+
29
+ def update
30
+ if @inbox.update(inbox_params)
31
+ redirect_to @inbox, notice: "Inbox was successfully updated."
32
+ else
33
+ render :edit, status: :unprocessable_entity
34
+ end
35
+ end
36
+
37
+ def destroy
38
+ @inbox.destroy
39
+ redirect_to inboxes_url, notice: "Inbox was successfully destroyed."
40
+ end
41
+
42
+ private
43
+
44
+ def inbox_params
45
+ params.require(:inbox).permit(:name, :address)
46
+ end
47
+
48
+ def set_inbox
49
+ @inbox = Inbox.find(params[:id])
50
+ rescue ActiveRecord::RecordNotFound
51
+ redirect_to po_box_inboxes_path, notice: "Inbox not found"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,4 @@
1
+ module PoBox
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module PoBox
2
+ module EmailsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module PoBox
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationMailbox < ActionMailbox::Base
2
+ end
@@ -0,0 +1,54 @@
1
+ module PoBox
2
+ class InboxMailbox < ApplicationMailbox
3
+ before_processing :ensure_inbox
4
+
5
+ def process
6
+ email = create_email
7
+ handle_attachments(email) if mail.attachments.any?
8
+ end
9
+
10
+ private
11
+
12
+ def ensure_inbox
13
+ return if inbox.present?
14
+
15
+ bounce_with EmailableMailer.missing_emailable(mail)
16
+ end
17
+
18
+ def inbox
19
+ @inbox ||= PoBox::Inbox.find_by(address: address)
20
+ end
21
+
22
+ def address
23
+ mail.to.first.split("@").first
24
+ end
25
+
26
+ def create_email
27
+ inbox.emails.create!(
28
+ cc: mail.cc,
29
+ from: mail.from.first,
30
+ multipart: mail.multipart?,
31
+ read: false,
32
+ subject: mail.subject,
33
+ to: mail.to.first,
34
+ message_id: mail.message_id,
35
+ body: if mail.html_part
36
+ mail.html_part.decoded
37
+ elsif mail.text_part
38
+ mail.text_part.decoded
39
+ else
40
+ mail.decoded
41
+ end
42
+ )
43
+ end
44
+
45
+ def handle_attachments(email)
46
+ mail.attachments.each do |attachment|
47
+ email.files.attach(
48
+ io: StringIO.new(attachment.body.decoded),
49
+ filename: attachment.filename
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,6 @@
1
+ module PoBox
2
+ class ApplicationMailer < ActionMailer::Base
3
+ # default from: "from@example.com"
4
+ # layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ module PoBox
2
+ class EmailableMailer < ApplicationMailer
3
+ def missing_emailable(mail)
4
+ mail(
5
+ to: mail.sender,
6
+ subject: "Inbox not found",
7
+ body: "We were unable to find an inbox for this email. Please contact support.",
8
+ content_type: "text/html"
9
+ )
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ module PoBox
2
+ module Emailable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :inboxes, as: :emailable, class_name: "PoBox::Inbox", dependent: :destroy
7
+
8
+ after_create_commit :set_inbox
9
+
10
+ accepts_nested_attributes_for :inboxes, allow_destroy: true
11
+
12
+ private
13
+
14
+ def set_inbox
15
+ return if inboxes.any?
16
+
17
+ inbox_address = if email.present?
18
+ "#{email.split("@").first}##{SecureRandom.hex(2)}"
19
+ else
20
+ SecureRandom.hex(8)
21
+ end
22
+
23
+ inbox = inboxes.new
24
+ loop do
25
+ inbox.address = inbox_address
26
+ break unless PoBox.emailable_class.joins(:inboxes).where(po_box_inboxes: {address: inbox.address}).exists?
27
+ end
28
+
29
+ save
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module PoBox
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module PoBox
2
+ class Email < ApplicationRecord
3
+ belongs_to :inbox, class_name: "PoBox::Inbox"
4
+
5
+ has_many_attached :files
6
+
7
+ validates :from, presence: true
8
+
9
+ delegate :raw_source, to: :body
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module PoBox
2
+ class Inbox < ApplicationRecord
3
+ belongs_to :emailable, polymorphic: true, class_name: PoBox.emailable_class.to_s
4
+
5
+ has_many :emails, class_name: "PoBox::Email", dependent: :destroy
6
+
7
+ define_method PoBox.emailable_class.to_s.underscore do
8
+ emailable
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Active email</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ <%= stylesheet_link_tag "po_box/application", media: "all" %>
8
+ </head>
9
+ <body>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+ <body>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,49 @@
1
+ <div id="<%= dom_id email %>">
2
+ <% if email.cc %>
3
+ <p>
4
+ <strong>Cc:</strong>
5
+ <%= email.cc %>
6
+ </p>
7
+ <% end %>
8
+ <p>
9
+ <strong>Subject:</strong>
10
+ <%= email.subject %>
11
+ </p>
12
+ <p>
13
+ <strong>From:</strong>
14
+ <%= email.from %>
15
+ </p>
16
+ <p>
17
+ <strong>To:</strong>
18
+ <%= email.to %>
19
+ </p>
20
+ <p>
21
+ <strong>Body:</strong>
22
+ <%= email.body %>
23
+ </p>
24
+ <% email.files.each do |file| %>
25
+ <div>
26
+ <% if file.representable? %>
27
+ <%= image_tag main_app.url_for(file.representation(resize_to_limit: [100, 100])) %>
28
+ <% else %>
29
+ <%= link_to file.filename, main_app.url_for(file) %>
30
+ <% end %>
31
+ </div>
32
+ <% end %>
33
+ <p>
34
+ <strong>Multipart:</strong>
35
+ <%= email.multipart %>
36
+ </p>
37
+ <p>
38
+ <strong>Emailable ID:</strong>
39
+ <%= email.inbox_id %>
40
+ </p>
41
+ <p>
42
+ <strong>Read:</strong>
43
+ <%= email.read %>
44
+ </p>
45
+ <p>
46
+ <strong>Message ID:</strong>
47
+ <%= email.message_id %>
48
+ </p>
49
+ </div>
@@ -0,0 +1,51 @@
1
+ <%= form_with(model: email, url: url) do |form| %>
2
+ <% if email.errors.any? %>
3
+ <div style="color: red">
4
+ <h2><%= pluralize(email.errors.count, "error") %> prohibited this email from being saved:</h2>
5
+ <ul>
6
+ <% email.errors.each do |error| %>
7
+ <li><%= error.full_message %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
12
+ <div>
13
+ <%= form.label :inbox_id, style: "display: block" %>
14
+ <%= form.text_field :inbox_id %>
15
+ </div>
16
+ <div>
17
+ <%= form.label :cc, style: "display: block" %>
18
+ <%= form.text_field :cc %>
19
+ </div>
20
+ <div>
21
+ <%= form.label :from, style: "display: block" %>
22
+ <%= form.text_field :from %>
23
+ </div>
24
+ <div>
25
+ <%= form.label :multipart, style: "display: block" %>
26
+ <%= form.check_box :multipart %>
27
+ </div>
28
+ <div>
29
+ <%= form.label :read, style: "display: block" %>
30
+ <%= form.check_box :read %>
31
+ </div>
32
+ <div>
33
+ <%= form.label :subject, style: "display: block" %>
34
+ <%= form.text_field :subject %>
35
+ </div>
36
+ <div>
37
+ <%= form.label :to, style: "display: block" %>
38
+ <%= form.text_field :to %>
39
+ </div>
40
+ <div>
41
+ <%= form.label :message_id, style: "display: block" %>
42
+ <%= form.text_field :message_id %>
43
+ </div>
44
+ <div>
45
+ <%= form.label :body, style: "display: block" %>
46
+ <%= form.text_area :body %>
47
+ </div>
48
+ <div>
49
+ <%= form.submit %>
50
+ </div>
51
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <h1>Editing email</h1>
2
+ <%= render "form", email: @email, url: inbox_email_path(@inbox, @email) %>
3
+ <br>
4
+ <div>
5
+ <%= link_to "Show this email", inbox_email_path(@inbox, @email) %> |
6
+ <%= link_to "Back to emails", inbox_emails_path(@inbox) %>
7
+ </div>
@@ -0,0 +1,11 @@
1
+ <p style="color: green"><%= notice %></p>
2
+ <h1>Emails</h1>
3
+ <div id="emails">
4
+ <% @emails.each do |email| %>
5
+ <%= render email %>
6
+ <p>
7
+ <%= link_to "Show this email", inbox_email_path(@inbox, email) %>
8
+ </p>
9
+ <% end %>
10
+ </div>
11
+ <%= link_to "New email", new_inbox_email_path(@inbox) %>
@@ -0,0 +1,6 @@
1
+ <h1>New email</h1>
2
+ <%= render "form", email: @email, url: inbox_emails_path(@inbox) %>
3
+ <br>
4
+ <div>
5
+ <%= link_to "Back to emails", inbox_emails_path(@inbox) %>
6
+ </div>
@@ -0,0 +1,7 @@
1
+ <p style="color: green"><%= notice %></p>
2
+ <%= render @email %>
3
+ <div>
4
+ <%= link_to "Edit this email", edit_inbox_email_path(@inbox, @email) %> |
5
+ <%= link_to "Back to emails", inbox_emails_path(@inbox) %>
6
+ <%= button_to "Destroy this email", inbox_email_path(@inbox, @email, method: :delete) %>
7
+ </div>
@@ -0,0 +1,2 @@
1
+ <h1>Home#index</h1>
2
+ <p>Find me in app/views/po_box/home/index.html.erb</p>
@@ -0,0 +1,19 @@
1
+ <%= form_with(model: inbox) do |form| %>
2
+ <% if inbox.errors.any? %>
3
+ <div style="color: red">
4
+ <h2><%= pluralize(inbox.errors.count, "error") %> prohibited this inbox from being saved:</h2>
5
+ <ul>
6
+ <% inbox.errors.each do |error| %>
7
+ <li><%= error.full_message %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
12
+ <div>
13
+ <%= form.label :address, style: "display: block" %>
14
+ <%= form.text_field :address %>
15
+ </div>
16
+ <div>
17
+ <%= form.submit %>
18
+ </div>
19
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <div id="<%= dom_id inbox %>">
2
+ <p>
3
+ <strong>Email Address:</strong>
4
+ <%= inbox.address %>
5
+ </p>
6
+ </div>
@@ -0,0 +1,7 @@
1
+ <h1>Editing inbox</h1>
2
+ <%= render "form", inbox: @inbox %>
3
+ <br>
4
+ <div>
5
+ <%= link_to "Show this inbox", @inbox %> |
6
+ <%= link_to "Back to inboxes", inboxes_path %>
7
+ </div>
@@ -0,0 +1,15 @@
1
+ <p style="color: green"><%= notice %></p>
2
+ <h1>Email addresses</h1>
3
+ <div class="">
4
+ <%= link_to "New inbox", new_inbox_path %>
5
+ </div>
6
+ <div id="inboxes">
7
+ <% @inboxes.each do |inbox| %>
8
+ <%= render inbox %>
9
+ <div>
10
+ <%= link_to "Show this inbox", inbox %>
11
+ <%= link_to "Edit inbox", edit_inbox_path(inbox) %>
12
+ <%= button_to "Destroy", edit_inbox_path(inbox) %>
13
+ </div>
14
+ <% end %>
15
+ </div>
@@ -0,0 +1,6 @@
1
+ <h1>New inbox</h1>
2
+ <%= render "form", inbox: @inbox %>
3
+ <br>
4
+ <div>
5
+ <%= link_to "Back to inboxes", inboxes_path %>
6
+ </div>
@@ -0,0 +1,8 @@
1
+ <p style="color: green"><%= notice %></p>
2
+ <%= render @inbox %>
3
+ <%= link_to "Emails", inbox_emails_path(@inbox) %> |
4
+ <div>
5
+ <%= link_to "Edit this inbox", edit_inbox_path() %> |
6
+ <%= link_to "Back to inboxes", inboxes_path %>
7
+ <%= button_to "Destroy this inbox", @inbox, method: :delete %>
8
+ </div>
@@ -0,0 +1 @@
1
+ PoBox.emailable_class = "Owner"
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ PoBox::Engine.routes.draw do
2
+ resources :inboxes do
3
+ resources :emails
4
+ end
5
+
6
+ root "inboxes#index"
7
+ end
@@ -0,0 +1,10 @@
1
+ class CreatePoBoxInboxes < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :po_box_inboxes do |t|
4
+ t.references :emailable, polymorphic: true, null: false
5
+ t.string :address, null: false, index: {unique: true}
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ class CreatePoBoxEmails < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :po_box_emails do |t|
4
+ t.references :inbox, foreign_key: {to_table: :po_box_inboxes}
5
+ t.string :cc
6
+ t.string :from
7
+ t.boolean :multipart
8
+ t.boolean :read, default: false, null: false
9
+ t.string :subject
10
+ t.string :to
11
+ t.string :message_id
12
+ t.text :body
13
+
14
+ t.timestamps
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate po_box User
6
+
7
+ This will create:
8
+ An initializer in config/initializers/po_box.rb
9
+ A migration to add the inbox address to the emailable table
10
+ Mount the engine in config/routes.rb
11
+ Add the emailable concern to the User model
12
+ Add the route all emails to the po_box/inbox mailbox
@@ -0,0 +1,33 @@
1
+ class PoBoxGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path("templates", __dir__)
3
+
4
+ def setup_initializer
5
+ create_file "config/initializers/po_box.rb", "PoBox.emailable_class = \"#{name.camelize}\""
6
+ end
7
+
8
+ def mount_engine
9
+ route "mount PoBox::Engine, at: \"/po_box\""
10
+ end
11
+
12
+ def include_concerns
13
+ inject_into_file "app/models/#{name.tableize.singularize}.rb", after: "class #{name.camelize} < ApplicationRecord\n" do
14
+ <<-RUBY
15
+ include PoBox::Emailable
16
+ RUBY
17
+ end
18
+ end
19
+
20
+ def setup_inbox
21
+ inject_into_file "app/mailboxes/application_mailbox.rb", after: "class ApplicationMailbox < ActionMailbox::Base\n" do
22
+ <<-RUBY
23
+ routing all: "po_box/inbox"
24
+ RUBY
25
+ end
26
+ end
27
+
28
+ def append_to_manifest
29
+ append_to_file "app/assets/config/manifest.js" do
30
+ "//= link po_box/application.css\n"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1 @@
1
+ PoBox.emailable_class = ARGV[0]
@@ -0,0 +1,13 @@
1
+ module PoBox
2
+ mattr_accessor :application_controller
3
+
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace PoBox
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ g.fixture_replacement :factory_bot
10
+ g.factory_bot dir: "spec/factories"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module PoBox
2
+ VERSION = "0.1.0"
3
+ end
data/lib/po_box.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "po_box/version"
2
+ require "po_box/engine"
3
+
4
+ module PoBox
5
+ mattr_accessor :emailable_class
6
+
7
+ def self.emailable_class
8
+ @@emailable_class.constantize
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ namespace :po_box do
2
+ desc "Backfill inboxes for emailables"
3
+ task backfill_emailables: :environment do
4
+ PoBox.emailable_class.all.each do |emailable|
5
+ emailable.send(:set_inbox)
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,232 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: po_box
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Leo Policastro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.4.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.4.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: factory_bot_rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: standard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: overcommit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: faker
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: shoulda-matchers
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: coderay
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: Create inboxes for your users to send and receive emails.
154
+ email:
155
+ - lbpdev@proton.me
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - MIT-LICENSE
161
+ - README.md
162
+ - Rakefile
163
+ - app/assets/config/po_box_manifest.js
164
+ - app/assets/stylesheets/po_box/application.css
165
+ - app/controllers/po_box/application_controller.rb
166
+ - app/controllers/po_box/emails_controller.rb
167
+ - app/controllers/po_box/inboxes_controller.rb
168
+ - app/helpers/po_box/application_helper.rb
169
+ - app/helpers/po_box/emails_helper.rb
170
+ - app/jobs/po_box/application_job.rb
171
+ - app/mailboxes/application_mailbox.rb
172
+ - app/mailboxes/po_box/inbox_mailbox.rb
173
+ - app/mailers/po_box/application_mailer.rb
174
+ - app/mailers/po_box/emailable_mailer.rb
175
+ - app/models/concerns/po_box/emailable.rb
176
+ - app/models/po_box/application_record.rb
177
+ - app/models/po_box/email.rb
178
+ - app/models/po_box/inbox.rb
179
+ - app/views/layouts/po_box/application.html.erb
180
+ - app/views/layouts/po_box/mailer.html.erb
181
+ - app/views/layouts/po_box/mailer.text.erb
182
+ - app/views/po_box/emails/_email.html.erb
183
+ - app/views/po_box/emails/_form.html.erb
184
+ - app/views/po_box/emails/edit.html.erb
185
+ - app/views/po_box/emails/index.html.erb
186
+ - app/views/po_box/emails/new.html.erb
187
+ - app/views/po_box/emails/show.html.erb
188
+ - app/views/po_box/home/index.html.erb
189
+ - app/views/po_box/inboxes/_form.html.erb
190
+ - app/views/po_box/inboxes/_inbox.html.erb
191
+ - app/views/po_box/inboxes/edit.html.erb
192
+ - app/views/po_box/inboxes/index.html.erb
193
+ - app/views/po_box/inboxes/new.html.erb
194
+ - app/views/po_box/inboxes/show.html.erb
195
+ - config/initializers/po_box.rb
196
+ - config/routes.rb
197
+ - db/migrate/20230425172819_create_po_box_inboxes.rb
198
+ - db/migrate/20230426005820_create_po_box_emails.rb
199
+ - lib/generators/po_box/USAGE
200
+ - lib/generators/po_box/po_box_generator.rb
201
+ - lib/generators/po_box/templates/po_box.rb
202
+ - lib/po_box.rb
203
+ - lib/po_box/engine.rb
204
+ - lib/po_box/version.rb
205
+ - lib/tasks/po_box.rake
206
+ homepage: https://github.com/leopolicastro/po_box
207
+ licenses:
208
+ - MIT
209
+ metadata:
210
+ homepage_uri: https://github.com/leopolicastro/po_box
211
+ source_code_uri: https://github.com/leopolicastro/po_box
212
+ changelog_uri: https://github.com/leopolicastro/po_box/blob/main/CHANGELOG.md
213
+ post_install_message:
214
+ rdoc_options: []
215
+ require_paths:
216
+ - lib
217
+ required_ruby_version: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ required_rubygems_version: !ruby/object:Gem::Requirement
223
+ requirements:
224
+ - - ">="
225
+ - !ruby/object:Gem::Version
226
+ version: '0'
227
+ requirements: []
228
+ rubygems_version: 3.4.10
229
+ signing_key:
230
+ specification_version: 4
231
+ summary: Create inboxes for your users to send and receive emails.
232
+ test_files: []