├── .dockerignore
├── test
├── keila
│ ├── contacts
│ │ ├── import_malformed2.csv
│ │ ├── import_external_ids.csv
│ │ ├── import_external_ids_duplicate.csv
│ │ ├── import_malformed1.csv
│ │ ├── import_with_data.csv
│ │ ├── import_rfc_4180_upsert.csv
│ │ └── import_rfc_4180_with_status.csv
│ └── files
│ │ ├── keila.jpg
│ │ └── keila.png
├── test_helper.exs
└── keila_web
│ ├── views
│ ├── layout_view_test.exs
│ └── error_view_test.exs
│ ├── helpers
│ └── hcaptcha_test.exs
│ └── controllers
│ └── auth_controller_login_disabled_test.exs
├── .tool-versions
├── lib
├── keila_web
│ ├── templates
│ │ ├── public_form
│ │ │ ├── success.html.heex
│ │ │ ├── show.html.heex
│ │ │ ├── unsubscribe.html.heex
│ │ │ ├── double_opt_in_required.html.heex
│ │ │ ├── double_opt_in_cancelled.html.heex
│ │ │ └── double_opt_in_failure.html.heex
│ │ ├── sender
│ │ │ ├── _local_config.html.heex
│ │ │ ├── _shared_local_config.html.heex
│ │ │ ├── verification_failure.html.heex
│ │ │ └── verification_success.html.heex
│ │ ├── layout
│ │ │ └── live.html.heex
│ │ ├── auth
│ │ │ ├── 404.html.heex
│ │ │ ├── register_success.html.heex
│ │ │ ├── activate_resend_success.html.heex
│ │ │ ├── reset_success.html.heex
│ │ │ ├── registration_disabled.html.heex
│ │ │ ├── activate_success.html.heex
│ │ │ └── reset_change_password_success.html.heex
│ │ └── project
│ │ │ └── _empty_state_campaigns.html.heex
│ ├── views
│ │ ├── auth_view.ex
│ │ ├── account_view.ex
│ │ ├── api_key_view.ex
│ │ ├── project_view.ex
│ │ ├── component_view.ex
│ │ ├── instance_admin_view.ex
│ │ ├── user_admin_view.ex
│ │ ├── form_view.ex
│ │ ├── public_form_layout_view.ex
│ │ ├── shared_sender_admin_view.ex
│ │ ├── error_view.ex
│ │ ├── campaign_view.ex
│ │ └── contact_view.ex
│ ├── api
│ │ ├── validators
│ │ │ └── api_contact_filter_validator.ex
│ │ ├── views
│ │ │ ├── api_sender_view.ex
│ │ │ └── api_segment_view.ex
│ │ └── plugs
│ │ │ └── api_authorization_plug.ex
│ └── helpers
│ │ ├── instance_info_plug.ex
│ │ ├── meta
│ │ └── meta_plug.ex
│ │ ├── project
│ │ └── project_plug.ex
│ │ └── plain_text_json_parser
│ │ └── plain_text_json_parser.ex
└── keila
│ ├── contacts
│ └── import_error.ex
│ ├── cldr.ex
│ ├── instance
│ ├── schemas
│ │ └── instance.ex
│ └── update_cron_worker.ex
│ ├── pipe_helpers.ex
│ ├── auth
│ └── schemas
│ │ ├── role_permission.ex
│ │ ├── permission.ex
│ │ └── role.ex
│ ├── mailings
│ ├── deliver_scheduled_campaigns_worker.ex
│ ├── sender_adapters
│ │ ├── local.ex
│ │ ├── shared_local.ex
│ │ └── sendgrid.ex
│ └── schemas
│ │ └── campaign_settings.ex
│ ├── billing
│ └── schemas
│ │ └── plan.ex
│ ├── accounts
│ └── schemas
│ │ ├── account.ex
│ │ └── credit.ex
│ └── tracking
│ └── schemas
│ ├── link.ex
│ └── click.ex
├── assets
├── static
│ ├── favicon.ico
│ ├── images
│ │ ├── pixel.gif
│ │ └── og_image.png
│ ├── fonts
│ │ └── inter
│ │ │ ├── Inter-Bold.woff
│ │ │ ├── Inter-Thin.woff
│ │ │ ├── Inter-Black.woff
│ │ │ ├── Inter-Black.woff2
│ │ │ ├── Inter-Bold.woff2
│ │ │ ├── Inter-Italic.woff
│ │ │ ├── Inter-Light.woff
│ │ │ ├── Inter-Light.woff2
│ │ │ ├── Inter-Medium.woff
│ │ │ ├── Inter-Thin.woff2
│ │ │ ├── Inter-ExtraBold.woff
│ │ │ ├── Inter-Italic.woff2
│ │ │ ├── Inter-Medium.woff2
│ │ │ ├── Inter-Regular.woff
│ │ │ ├── Inter-Regular.woff2
│ │ │ ├── Inter-SemiBold.woff
│ │ │ ├── Inter-SemiBold.woff2
│ │ │ ├── Inter-BlackItalic.woff
│ │ │ ├── Inter-BoldItalic.woff
│ │ │ ├── Inter-BoldItalic.woff2
│ │ │ ├── Inter-ExtraBold.woff2
│ │ │ ├── Inter-ExtraLight.woff
│ │ │ ├── Inter-ExtraLight.woff2
│ │ │ ├── Inter-LightItalic.woff
│ │ │ ├── Inter-ThinItalic.woff
│ │ │ ├── Inter-ThinItalic.woff2
│ │ │ ├── Inter-italic.var.woff2
│ │ │ ├── Inter-roman.var.woff2
│ │ │ ├── Inter-BlackItalic.woff2
│ │ │ ├── Inter-LightItalic.woff2
│ │ │ ├── Inter-MediumItalic.woff
│ │ │ ├── Inter-MediumItalic.woff2
│ │ │ ├── Inter-SemiBoldItalic.woff
│ │ │ ├── Inter-ExtraBoldItalic.woff
│ │ │ ├── Inter-ExtraBoldItalic.woff2
│ │ │ ├── Inter-ExtraLightItalic.woff
│ │ │ ├── Inter-SemiBoldItalic.woff2
│ │ │ └── Inter-ExtraLightItalic.woff2
│ ├── social-icons
│ │ ├── xing-black.png
│ │ ├── xing-white.png
│ │ ├── github-black.png
│ │ ├── github-white.png
│ │ ├── reddit-black.png
│ │ ├── reddit-white.png
│ │ ├── signal-black.png
│ │ ├── signal-white.png
│ │ ├── slack-black.png
│ │ ├── slack-white.png
│ │ ├── tiktok-black.png
│ │ ├── tiktok-white.png
│ │ ├── twitch-black.png
│ │ ├── twitch-white.png
│ │ ├── bluesky-black.png
│ │ ├── bluesky-white.png
│ │ ├── discord-black.png
│ │ ├── discord-white.png
│ │ ├── discourse-black.png
│ │ ├── discourse-white.png
│ │ ├── facebook-black.png
│ │ ├── facebook-white.png
│ │ ├── instagram-black.png
│ │ ├── instagram-white.png
│ │ ├── linkedin-black.png
│ │ ├── linkedin-white.png
│ │ ├── mastodon-black.png
│ │ ├── mastodon-white.png
│ │ ├── snapchat-black.png
│ │ ├── snapchat-white.png
│ │ ├── threads-black.png
│ │ ├── threads-white.png
│ │ ├── whatsapp-black.png
│ │ ├── whatsapp-white.png
│ │ ├── x-twitter-black.png
│ │ ├── x-twitter-white.png
│ │ ├── youtube-black.png
│ │ ├── youtube-white.png
│ │ ├── soundcloud-black.png
│ │ └── soundcloud-white.png
│ ├── downloads
│ │ ├── keila_import_template.ods
│ │ └── keila_import_template.csv
│ └── robots.txt
├── postcss.config.js
├── css
│ ├── app.scss
│ └── _header.scss
├── js
│ ├── campaign-editors
│ │ ├── markdown
│ │ │ └── markdown-serializer.js
│ │ └── block
│ │ │ └── blocks
│ │ │ └── separator.js
│ └── hooks
│ │ ├── campaign-settings-dialog.js
│ │ ├── remember-unsaved.js
│ │ └── file_manager.js
└── tailwind.config.js
├── priv
├── repo
│ └── migrations
│ │ ├── .formatter.exs
│ │ ├── 20220324085800_add_user_locale.exs
│ │ ├── 20240524220616_add_mjml_body.exs
│ │ ├── 20211101230000_add_contact_custom_data.exs
│ │ ├── 20230111222000_add_campaign_json_body.exs
│ │ ├── 20230308111500_add_campaign_preview_text.exs
│ │ ├── 20210809112000_add_recipient_receipt.exs
│ │ ├── 20230104115500_add_recipient_failed_at.exs
│ │ ├── 20241227072034_add_parent_account.exs
│ │ ├── 20210225161207_upgrade_oban_jobs_to_v10.exs
│ │ ├── 20220825151500_upgrade_oban_jobs_to_v11.exs
│ │ ├── 20231210140318_add_contact_double_opt_in_timestamp.exs
│ │ ├── 20250407180538_add_user_name.exs
│ │ ├── 20250513010703_add_campaign_public_link.exs
│ │ ├── 20210105112000_projects.exs
│ │ ├── 20250223090326_add_contact_external_id.exs
│ │ ├── 20210104175000_root_group.exs
│ │ ├── 20250407180947_add_account_data.exs
│ │ ├── 20231203235509_add_form_double_opt_in_settings.exs
│ │ ├── 20241117001430_create_instance.exs
│ │ ├── 20210121100329_add_oban_jobs_table.exs
│ │ ├── 20240809143953_add_recipient_queued_at.exs
│ │ ├── 20210816142200_default_contact_status.exs
│ │ ├── 20210428165000_shared_senders.exs
│ │ ├── 20210222154306_add_deliver_at_to_campaigns.exs
│ │ ├── 20220114154000_add_campaign_custom_data.exs
│ │ ├── 20230120223000_add_segment_default_filter.exs
│ │ ├── 20211116040700_segmentations.exs
│ │ ├── 20210630205000_subscription_upserts.exs
│ │ ├── 20250220063317_add_accounts_credit_transactions_on_delete.exs
│ │ ├── 20210311225200_templates.exs
│ │ ├── 20230604141200_add_contacts_form_params_table.exs
│ │ ├── 20221227145500_add_recipient_states.exs
│ │ ├── 20220523235000_add_files.exs
│ │ ├── 20220905125000_emails_to_citext.exs
│ │ ├── 20210319145300_form_timestamps.exs
│ │ └── 20250707201249_add_verified_from_email_to_senders.exs
├── gettext
│ ├── de
│ │ └── LC_MESSAGES
│ │ │ ├── auth.mo
│ │ │ ├── cloud.mo
│ │ │ ├── default.mo
│ │ │ └── errors.mo
│ └── fr
│ │ └── LC_MESSAGES
│ │ └── default.mo
├── email_templates
│ ├── hybrid
│ │ ├── _block--separator.liquid
│ │ ├── _block--markdown.liquid
│ │ ├── _block--paragraph.liquid
│ │ ├── _block--heading.liquid
│ │ ├── _block--list.liquid
│ │ ├── _block--quote.liquid
│ │ ├── _block--list_items.liquid
│ │ └── _block--button.liquid
│ └── default-text-content.txt
└── vendor
│ ├── hero-icons-outline
│ ├── minus.svg
│ ├── check.svg
│ ├── minus-sm.svg
│ ├── chevron-right.svg
│ ├── chevron-up.svg
│ ├── menu-alt-4.svg
│ ├── x.svg
│ ├── chevron-down.svg
│ ├── chevron-left.svg
│ ├── menu.svg
│ ├── plus.svg
│ ├── arrow-right.svg
│ ├── arrow-up.svg
│ ├── menu-alt-1.svg
│ ├── menu-alt-2.svg
│ ├── menu-alt-3.svg
│ ├── plus-sm.svg
│ ├── selector.svg
│ ├── arrow-down.svg
│ ├── arrow-left.svg
│ ├── arrow-narrow-up.svg
│ ├── lightning-bolt.svg
│ ├── view-list.svg
│ ├── arrow-narrow-down.svg
│ ├── arrow-narrow-left.svg
│ ├── arrow-narrow-right.svg
│ ├── chevron-double-right.svg
│ ├── chevron-double-up.svg
│ ├── code.svg
│ ├── hashtag.svg
│ ├── reply.svg
│ ├── trending-down.svg
│ ├── trending-up.svg
│ ├── chevron-double-down.svg
│ ├── chevron-double-left.svg
│ ├── clock.svg
│ ├── paper-airplane.svg
│ ├── pause.svg
│ ├── search.svg
│ ├── minus-circle.svg
│ ├── shopping-bag.svg
│ ├── bookmark.svg
│ ├── check-circle.svg
│ ├── sort-ascending.svg
│ ├── sort-descending.svg
│ ├── zoom-out.svg
│ ├── switch-horizontal.svg
│ ├── switch-vertical.svg
│ ├── cube.svg
│ ├── download.svg
│ ├── exclamation-circle.svg
│ ├── plus-circle.svg
│ ├── upload.svg
│ ├── arrow-circle-down.svg
│ ├── arrow-circle-right.svg
│ ├── arrow-circle-up.svg
│ ├── arrow-uturn-left.svg
│ ├── information-circle.svg
│ ├── library.svg
│ ├── user.svg
│ ├── arrow-circle-left.svg
│ ├── folder.svg
│ ├── rss.svg
│ ├── x-circle.svg
│ ├── zoom-in.svg
│ ├── currency-rupee.svg
│ ├── currency-yen.svg
│ ├── dots-circle-horizontal.svg
│ ├── flag.svg
│ ├── archive.svg
│ ├── currency-pound.svg
│ ├── device-mobile.svg
│ ├── device-tablet.svg
│ ├── external-link.svg
│ ├── folder-remove.svg
│ ├── moon.svg
│ ├── calendar.svg
│ ├── cloud.svg
│ ├── emoji-happy.svg
│ ├── emoji-sad.svg
│ ├── folder-add.svg
│ ├── logout.svg
│ ├── save.svg
│ ├── table.svg
│ ├── terminal.svg
│ ├── user-add.svg
│ ├── user-remove.svg
│ ├── ban.svg
│ ├── bookmark-alt.svg
│ ├── cloud-download.svg
│ ├── cloud-upload.svg
│ ├── credit-card.svg
│ ├── login.svg
│ ├── annotation.svg
│ ├── arrows-expand.svg
│ ├── chart-square-bar.svg
│ ├── lock-open.svg
│ ├── mail.svg
│ ├── pencil.svg
│ ├── presentation-chart-bar.svg
│ ├── presentation-chart-line.svg
│ ├── currency-bangladeshi.svg
│ ├── folder-download.svg
│ ├── receipt-refund.svg
│ ├── search-circle.svg
│ ├── chat-alt.svg
│ ├── document.svg
│ ├── lock-closed.svg
│ ├── microphone.svg
│ ├── refresh.svg
│ ├── sparkles.svg
│ ├── users.svg
│ ├── at-symbol.svg
│ ├── film.svg
│ ├── ticket.svg
│ ├── desktop-computer.svg
│ ├── document-remove.svg
│ ├── key.svg
│ ├── document-add.svg
│ ├── document-text.svg
│ ├── dots-vertical.svg
│ ├── duplicate.svg
│ ├── heart.svg
│ ├── paper-clip.svg
│ ├── pencil-alt.svg
│ ├── tag.svg
│ ├── translate.svg
│ ├── trash.svg
│ ├── chip.svg
│ ├── dots-horizontal.svg
│ ├── folder-open.svg
│ ├── link.svg
│ ├── clipboard.svg
│ ├── document-report.svg
│ ├── user-circle.svg
│ ├── wifi.svg
│ ├── backspace.svg
│ ├── cursor-click.svg
│ ├── document-download.svg
│ ├── exclamation.svg
│ ├── gift.svg
│ ├── newspaper.svg
│ ├── video-camera.svg
│ ├── adjustments.svg
│ ├── cash.svg
│ ├── database.svg
│ ├── home.svg
│ ├── scale.svg
│ ├── status-online.svg
│ ├── clipboard-check.svg
│ ├── office-building.svg
│ ├── save-as.svg
│ ├── sun.svg
│ ├── calculator.svg
│ ├── collection.svg
│ ├── fast-forward.svg
│ ├── filter.svg
│ ├── globe-alt.svg
│ ├── music-note.svg
│ ├── rewind.svg
│ ├── server.svg
│ ├── chat.svg
│ ├── map.svg
│ ├── photograph.svg
│ ├── shopping-cart.svg
│ ├── chat-alt-2.svg
│ ├── clipboard-copy.svg
│ ├── cube-transparent.svg
│ ├── document-search.svg
│ ├── support.svg
│ ├── clipboard-list.svg
│ ├── currency-euro.svg
│ ├── hand.svg
│ ├── question-mark-circle.svg
│ ├── receipt-tax.svg
│ ├── scissors.svg
│ ├── document-duplicate.svg
│ ├── printer.svg
│ ├── briefcase.svg
│ ├── chart-pie.svg
│ ├── currency-dollar.svg
│ ├── stop.svg
│ ├── bell.svg
│ ├── volume-up.svg
│ ├── inbox.svg
│ ├── chart-bar.svg
│ ├── color-swatch.svg
│ ├── globe.svg
│ ├── shield-check.svg
│ ├── template.svg
│ ├── speakerphone.svg
│ ├── view-boards.svg
│ ├── exclamation-triangle.svg
│ ├── inbox-in.svg
│ ├── location-marker.svg
│ ├── shield-exclamation.svg
│ ├── light-bulb.svg
│ ├── mail-open.svg
│ ├── phone.svg
│ ├── play.svg
│ ├── identification.svg
│ ├── view-grid-add.svg
│ ├── thumb-down.svg
│ ├── share.svg
│ ├── eye.svg
│ ├── thumb-up.svg
│ ├── variable.svg
│ ├── puzzle.svg
│ ├── book-open.svg
│ ├── phone-incoming.svg
│ ├── phone-outgoing.svg
│ ├── beaker.svg
│ ├── phone-missed-call.svg
│ ├── view-grid.svg
│ ├── qrcode.svg
│ ├── status-offline.svg
│ ├── user-group.svg
│ ├── volume-off.svg
│ ├── camera.svg
│ ├── finger-print.svg
│ ├── cake.svg
│ ├── fire.svg
│ ├── eye-off.svg
│ ├── truck.svg
│ ├── star.svg
│ ├── academic-cap.svg
│ └── badge-check.svg
│ ├── keila-icons
│ ├── horizontal_rule.svg
│ ├── indent-decrease.svg
│ ├── indent-increase.svg
│ ├── button-alt.svg
│ ├── list-unordered.svg
│ ├── block.svg
│ ├── logo.svg
│ └── mjml.svg
│ ├── social-icons
│ ├── x-twitter.svg
│ ├── twitch.svg
│ ├── tiktok.svg
│ ├── discourse.svg
│ ├── facebook.svg
│ ├── youtube.svg
│ ├── xing.svg
│ ├── bluesky.svg
│ └── linkedin.svg
│ └── font-awesome
│ ├── spinner.svg
│ ├── align-left.svg
│ ├── align-justify.svg
│ ├── align-right.svg
│ └── align-center.svg
├── .github
└── assets
│ ├── keila-20210705.jpg
│ ├── keila-20211203.jpg
│ ├── devcontainer-button.png
│ └── logo.svg
├── extra
├── keila
│ └── license
│ │ └── props.ex
├── license.public.pem
├── keila_cloud
│ ├── config
│ │ └── cloud_config.exs
│ └── schemas
│ │ └── account.ex
└── keila_cloud_web
│ ├── views
│ ├── cloud_admin_view.ex
│ └── cloud_sender_view.ex
│ ├── controllers
│ └── cloud_account_controller.ex
│ └── templates
│ └── cloud_account
│ └── _completed.html.heex
├── .formatter.exs
├── .zed
└── settings.json
└── dprint.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | _build
2 | deps
3 | assets/node_modules
4 | priv/static
--------------------------------------------------------------------------------
/test/keila/contacts/import_malformed2.csv:
--------------------------------------------------------------------------------
1 | Email, First name, Last name
2 | ,Foo,Bar
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | elixir 1.18.4-otp-27
2 | erlang 27.3
3 | dprint 0.50.0
4 | nodejs 20.19.2
5 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/public_form/success.html.heex:
--------------------------------------------------------------------------------
1 | <%= render_form_success(@form) %>
2 |
--------------------------------------------------------------------------------
/assets/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/favicon.ico
--------------------------------------------------------------------------------
/lib/keila_web/templates/public_form/show.html.heex:
--------------------------------------------------------------------------------
1 | <%= render_form(@form, @changeset, @mode) %>
2 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/public_form/unsubscribe.html.heex:
--------------------------------------------------------------------------------
1 | <%= render_unsubscribe_form(@form) %>
2 |
--------------------------------------------------------------------------------
/lib/keila_web/views/auth_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.AuthView do
2 | use KeilaWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:ecto_sql],
3 | inputs: ["*.exs"]
4 | ]
5 |
--------------------------------------------------------------------------------
/test/keila/files/keila.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/test/keila/files/keila.jpg
--------------------------------------------------------------------------------
/test/keila/files/keila.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/test/keila/files/keila.png
--------------------------------------------------------------------------------
/lib/keila_web/views/account_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.AccountView do
2 | use KeilaWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/keila_web/views/api_key_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ApiKeyView do
2 | use KeilaWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/lib/keila_web/views/project_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ProjectView do
2 | use KeilaWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/assets/static/images/pixel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/images/pixel.gif
--------------------------------------------------------------------------------
/lib/keila_web/views/component_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ComponentView do
2 | use KeilaWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/.github/assets/keila-20210705.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/.github/assets/keila-20210705.jpg
--------------------------------------------------------------------------------
/.github/assets/keila-20211203.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/.github/assets/keila-20211203.jpg
--------------------------------------------------------------------------------
/assets/static/images/og_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/images/og_image.png
--------------------------------------------------------------------------------
/extra/keila/license/props.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.License.Props do
2 | defstruct [:name, :email, :expires, :type]
3 | end
4 |
--------------------------------------------------------------------------------
/lib/keila_web/views/instance_admin_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.InstanceAdminView do
2 | use KeilaWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/priv/gettext/de/LC_MESSAGES/auth.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/priv/gettext/de/LC_MESSAGES/auth.mo
--------------------------------------------------------------------------------
/priv/gettext/de/LC_MESSAGES/cloud.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/priv/gettext/de/LC_MESSAGES/cloud.mo
--------------------------------------------------------------------------------
/.github/assets/devcontainer-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/.github/assets/devcontainer-button.png
--------------------------------------------------------------------------------
/priv/gettext/de/LC_MESSAGES/default.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/priv/gettext/de/LC_MESSAGES/default.mo
--------------------------------------------------------------------------------
/priv/gettext/de/LC_MESSAGES/errors.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/priv/gettext/de/LC_MESSAGES/errors.mo
--------------------------------------------------------------------------------
/priv/gettext/fr/LC_MESSAGES/default.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/priv/gettext/fr/LC_MESSAGES/default.mo
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Bold.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Thin.woff
--------------------------------------------------------------------------------
/assets/static/social-icons/xing-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/xing-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/xing-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/xing-white.png
--------------------------------------------------------------------------------
/lib/keila/contacts/import_error.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Contacts.ImportError do
2 | defexception [:message, :line, :column]
3 | end
4 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/public_form/double_opt_in_required.html.heex:
--------------------------------------------------------------------------------
1 | <%= render_form_double_opt_in_required(@form, @email) %>
2 |
--------------------------------------------------------------------------------
/lib/keila_web/views/user_admin_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.UserAdminView do
2 | use KeilaWeb, :view
3 | require Keila
4 | end
5 |
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Black.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Black.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Bold.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Italic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Light.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Light.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Medium.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Thin.woff2
--------------------------------------------------------------------------------
/assets/static/social-icons/github-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/github-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/github-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/github-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/reddit-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/reddit-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/reddit-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/reddit-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/signal-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/signal-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/signal-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/signal-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/slack-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/slack-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/slack-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/slack-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/tiktok-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/tiktok-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/tiktok-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/tiktok-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/twitch-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/twitch-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/twitch-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/twitch-white.png
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraBold.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Italic.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Medium.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Regular.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-Regular.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-SemiBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-SemiBold.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-SemiBold.woff2
--------------------------------------------------------------------------------
/assets/static/social-icons/bluesky-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/bluesky-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/bluesky-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/bluesky-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/discord-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/discord-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/discord-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/discord-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/discourse-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/discourse-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/discourse-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/discourse-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/facebook-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/facebook-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/facebook-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/facebook-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/instagram-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/instagram-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/instagram-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/instagram-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/linkedin-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/linkedin-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/linkedin-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/linkedin-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/mastodon-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/mastodon-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/mastodon-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/mastodon-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/snapchat-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/snapchat-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/snapchat-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/snapchat-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/threads-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/threads-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/threads-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/threads-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/whatsapp-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/whatsapp-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/whatsapp-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/whatsapp-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/x-twitter-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/x-twitter-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/x-twitter-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/x-twitter-white.png
--------------------------------------------------------------------------------
/assets/static/social-icons/youtube-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/youtube-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/youtube-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/youtube-white.png
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-BlackItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-BlackItalic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-BoldItalic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-BoldItalic.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraBold.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraLight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraLight.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraLight.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-LightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-LightItalic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ThinItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ThinItalic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ThinItalic.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-italic.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-italic.var.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-roman.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-roman.var.woff2
--------------------------------------------------------------------------------
/assets/static/social-icons/soundcloud-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/soundcloud-black.png
--------------------------------------------------------------------------------
/assets/static/social-icons/soundcloud-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/social-icons/soundcloud-white.png
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--separator.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/static/downloads/keila_import_template.ods:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/downloads/keila_import_template.ods
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-BlackItalic.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-LightItalic.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-MediumItalic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-MediumItalic.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-SemiBoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-SemiBoldItalic.woff
--------------------------------------------------------------------------------
/extra/license.public.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MCowBQYDK2VwAyEAZGDDgaezSrOe6kGtdxls7LH2gb+rF9Hnd8RKjFUxNtM=
3 | -----END PUBLIC KEY-----
4 |
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraBoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraBoldItalic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraBoldItalic.woff2
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraLightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraLightItalic.woff
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-SemiBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-SemiBoldItalic.woff2
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--markdown.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ block.data }}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/keila/contacts/import_external_ids.csv:
--------------------------------------------------------------------------------
1 | Email,External ID
2 | foo@example.com,
3 | foo@example.com,1
4 | foo2@example.com,1
5 | foo3@example.com,3
6 |
--------------------------------------------------------------------------------
/assets/static/fonts/inter/Inter-ExtraLightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pentacent/keila/HEAD/assets/static/fonts/inter/Inter-ExtraLightItalic.woff2
--------------------------------------------------------------------------------
/test/keila/contacts/import_external_ids_duplicate.csv:
--------------------------------------------------------------------------------
1 | Email,External ID
2 | foo@example.com,
3 | foo@example.com,1
4 | foo2@example.com
5 | foo2@example.com,1
6 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | Keila.Factory.start_counter()
2 | Ecto.Adapters.SQL.Sandbox.mode(Keila.Repo, :manual)
3 | ExUnit.configure(exclude: :skip)
4 | ExUnit.start()
5 |
--------------------------------------------------------------------------------
/lib/keila_web/views/form_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.FormView do
2 | use KeilaWeb, :view
3 | import KeilaWeb.PublicFormLayoutView, only: [build_styles: 1]
4 | end
5 |
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--paragraph.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{block.data.text }}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/keila/contacts/import_malformed1.csv:
--------------------------------------------------------------------------------
1 | Email, First name, Last name
2 | foo@example.com, Foo, Bar
3 | "foo2@example.com","Foo2","Bar2"
4 | foo3@example.com, "Foo 3", "Bar3"
5 |
--------------------------------------------------------------------------------
/test/keila/contacts/import_with_data.csv:
--------------------------------------------------------------------------------
1 | Email,First name,Last name,Data
2 | foo-1@example.com,Joël,Müller-Schultheiß,"{""foo"":""bar""}"
3 | foo-2@example.com,Eliška,Þorláksson,"{""foo"":[1,2,3]}"
4 |
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--heading.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ block.data.text }}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--list.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% render "block--list_items", style: block.data.style, items: block.data.items %}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/keila/contacts/import_rfc_4180_upsert.csv:
--------------------------------------------------------------------------------
1 | Email,First name,Last name
2 | joao@example.com,João,Milton
3 | elisa@example.com,Elisa,Paula
4 | foo-1@example.com,Foo,Bar
5 | foo-2@example.com,Foo,Bar
6 | joao@example.com,João,Nilton
--------------------------------------------------------------------------------
/lib/keila/cldr.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Cldr do
2 | use Cldr,
3 | otp_app: :keila,
4 | locales: Application.compile_env(:keila, KeilaWeb.Gettext)[:locales],
5 | default_locale: "en",
6 | providers: [Cldr.Territory]
7 | end
8 |
--------------------------------------------------------------------------------
/lib/keila/instance/schemas/instance.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Instance.Instance do
2 | use Keila.Schema
3 |
4 | schema "instance" do
5 | embeds_many :available_updates, Keila.Instance.Release, on_replace: :delete
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/minus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /c/
3 | Disallow: /r/
4 | Disallow: /forms/
5 | Disallow: /verify-sender/
6 | Disallow: /double-opt-in/
7 | Disallow: /unsubscribe/
8 | Disallow: /uploads/
9 | Disallow: /archive/
10 |
--------------------------------------------------------------------------------
/lib/keila/pipe_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.PipeHelpers do
2 | def tap_if_not_nil(value, fun)
3 | def tap_if_not_nil(nil, _), do: nil
4 |
5 | def tap_if_not_nil(value, fun) do
6 | fun.(value)
7 | value
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/minus-sm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/horizontal_rule.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/menu-alt-4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/x.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | plugins: [Phoenix.LiveView.HTMLFormatter],
3 | import_deps: [:ecto, :phoenix],
4 | inputs: ["*.{heex,ex,exs}", "priv/*/seeds.exs", "{config,lib,test,extra}/**/*.{heex,ex,exs}"],
5 | subdirectories: ["priv/*/migrations"]
6 | ]
7 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/keila/contacts/import_rfc_4180_with_status.csv:
--------------------------------------------------------------------------------
1 | Email,First name,Last name,Status
2 | active@example.com,Foo,Bar,active
3 | unsubscribed@example.com,Foo,Bar,unsubscribed
4 | unreachable@example.com,Foo,Bar,unreachable
5 | empty@example.com,Foo,Bar,
6 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/menu-alt-1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/menu-alt-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/menu-alt-3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/plus-sm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/selector.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-narrow-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/lightning-bolt.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/view-list.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220324085800_add_user_locale.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddUserLocale do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("users") do
6 | add :locale, :string, size: 6
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20240524220616_add_mjml_body.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddMjmlBody do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_campaigns") do
6 | add :mjml_body, :text
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-narrow-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-narrow-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-narrow-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-double-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-double-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/code.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/hashtag.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/reply.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/trending-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/trending-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/extra/keila_cloud/config/cloud_config.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | adapters = read_config(:keila)[Keila.Mailings.SenderAdapters][:adapters]
4 |
5 | config :keila, Keila.Mailings.SenderAdapters,
6 | adapters: [
7 | KeilaCloud.Mailings.SendWithKeila | adapters
8 | ]
9 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20211101230000_add_contact_custom_data.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddContactCustomData do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("contacts") do
6 | add :data, :jsonb
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-double-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chevron-double-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/clock.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/paper-airplane.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | syntax: "postcss-scss",
3 | plugins: [
4 | require("postcss-import"),
5 | require("tailwindcss/nesting"),
6 | require("tailwindcss")("./tailwind.config.js"),
7 | require("autoprefixer")
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/minus-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/shopping-bag.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/indent-decrease.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/indent-increase.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20230111222000_add_campaign_json_body.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddCampaignJsonBody do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_campaigns") do
6 | add :json_body, :json
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/bookmark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/check-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/sort-ascending.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/sort-descending.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/zoom-out.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--quote.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{block.data.text }}
5 | {{ block.data.caption }}
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/switch-horizontal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/switch-vertical.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20230308111500_add_campaign_preview_text.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddCampaignPreviewText do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_campaigns") do
6 | add :preview_text, :text
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cube.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/exclamation-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/plus-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/upload.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/sender/_local_config.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= gettext(
3 | "The local sender adapter is useful for testing during development. Inspect its mailbox at: %{url}",
4 | url: Routes.static_url(KeilaWeb.Endpoint, "/dev/mailbox")
5 | ) %>
6 |
7 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210809112000_add_recipient_receipt.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddRecipeintReceipt do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_recipients") do
6 | add :receipt, :string, length: 32
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20230104115500_add_recipient_failed_at.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddRecipientFailedAt do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_recipients") do
6 | add :failed_at, :utc_datetime
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-circle-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-circle-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-circle-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-uturn-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/information-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/library.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/user.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrow-circle-left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/folder.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/rss.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/x-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/zoom-in.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/button-alt.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20241227072034_add_parent_account.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddParentAccount do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:accounts) do
6 | add :parent_id, references("accounts", on_delete: :nilify_all)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/currency-rupee.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/currency-yen.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/dots-circle-horizontal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/flag.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210225161207_upgrade_oban_jobs_to_v10.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.UpgradeObanJobsToV10 do
2 | use Ecto.Migration
3 |
4 | def up do
5 | Oban.Migrations.up(version: 10)
6 | end
7 |
8 | def down do
9 | Oban.Migrations.down(version: 10)
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220825151500_upgrade_oban_jobs_to_v11.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.UpgradeObanJobsToV11 do
2 | use Ecto.Migration
3 |
4 | def up do
5 | Oban.Migrations.up(version: 11)
6 | end
7 |
8 | def down do
9 | Oban.Migrations.down(version: 11)
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20231210140318_add_contact_double_opt_in_timestamp.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddContactDoubleOptInTimestamp do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("contacts") do
6 | add :double_opt_in_at, :utc_datetime
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250407180538_add_user_name.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddUserName do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:users) do
6 | add :given_name, :string, size: 64
7 | add :family_name, :string, size: 64
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/archive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/currency-pound.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/device-mobile.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/device-tablet.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/external-link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/folder-remove.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/moon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/css/app.scss:
--------------------------------------------------------------------------------
1 | /*
2 | This is the main entrypoint for Tailwind/PostCSS.
3 | */
4 |
5 | @import "tailwindcss/base";
6 | @import "tailwindcss/components";
7 | @import "tailwindcss/utilities";
8 |
9 | @import "./phoenix.css";
10 |
11 | @import "./styles.scss";
12 | @import "./_markdown_wysiwyg.scss";
13 |
--------------------------------------------------------------------------------
/assets/js/campaign-editors/markdown/markdown-serializer.js:
--------------------------------------------------------------------------------
1 | import { defaultMarkdownSerializer } from "prosemirror-markdown"
2 |
3 | const markdownSerializer = defaultMarkdownSerializer
4 | markdownSerializer.marks.liquid = { open: "", close: "", mixable: false, escape: false }
5 |
6 | export { markdownSerializer }
7 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250513010703_add_campaign_public_link.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddCampaignPublicLink do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_campaigns") do
6 | add :public_link_enabled, :boolean, default: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/calendar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cloud.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/emoji-happy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/emoji-sad.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/folder-add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/logout.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/save.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/table.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/terminal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/user-add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/user-remove.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/ban.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/bookmark-alt.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cloud-download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cloud-upload.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/credit-card.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/login.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/static/downloads/keila_import_template.csv:
--------------------------------------------------------------------------------
1 | Email,First name,Last name,Data,External ID
2 | jane@example.com,Jane,Doe,"{""marketing_consent"": true, ""age"": 34, ""birthplace"": ""Paris"", ""tags"":[""rocket-scientist""]}",
3 | john@example.com,John,Smith,"{""birthplace"": ""Berlin"", ""tags"":[""book-enthusiast""]}",
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/annotation.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/arrows-expand.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chart-square-bar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/lock-open.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/mail.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/pencil.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/presentation-chart-bar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/presentation-chart-line.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/currency-bangladeshi.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/folder-download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/receipt-refund.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/search-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chat-alt.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/lock-closed.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/microphone.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/refresh.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/sparkles.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/users.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/keila_web/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.LayoutViewTest do
2 | use KeilaWeb.ConnCase, async: true
3 |
4 | # When testing helpers, you may want to import Phoenix.HTML and
5 | # use functions such as safe_to_string() to convert the helper
6 | # result into an HTML string.
7 | # import Phoenix.HTML
8 | end
9 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/at-symbol.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/film.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/ticket.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/public_form/double_opt_in_cancelled.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= render_h1(@form) %>
3 |
4 | <%= gettext("Your data has been deleted.") %>
5 |
6 |
7 | <%= gettext("You will not be subscribed to this list.") %>
8 |
9 | <%= render_fine_print(@form) %>
10 |
11 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/desktop-computer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document-remove.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/key.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/sender/_shared_local_config.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render_icon(:check_circle) %>
5 |
6 | <%= gettext("No configuration is required for this sender.") %>
7 |
8 |
9 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document-add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document-text.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/dots-vertical.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/duplicate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/heart.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/paper-clip.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/pencil-alt.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/tag.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/translate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/trash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210105112000_projects.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.Projects do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table("projects") do
6 | add :group_id, references("groups", on_delete: :delete_all, null: false)
7 | add :name, :string
8 | timestamps()
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chip.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/dots-horizontal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/folder-open.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/js/hooks/campaign-settings-dialog.js:
--------------------------------------------------------------------------------
1 | const CampaignSettingsDialogHook = {
2 | mounted() {
3 | this.handleEvent("settings_validated", ({ valid }) => {
4 | if (valid) {
5 | this.el.dispatchEvent(new CustomEvent("x-confirm", { detail: {} }))
6 | }
7 | })
8 | }
9 | }
10 |
11 | export { CampaignSettingsDialogHook }
12 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/clipboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document-report.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/user-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/wifi.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250223090326_add_contact_external_id.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddContactExternalId do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("contacts") do
6 | add :external_id, :string, size: 40
7 | end
8 |
9 | create unique_index("contacts", [:external_id, :project_id])
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/backspace.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cursor-click.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document-download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/exclamation.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/gift.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/newspaper.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/video-camera.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/adjustments.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/database.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/home.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/scale.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/status-online.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/layout/live.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= live_flash(@flash, :info) %>
3 |
4 |
5 |
6 | <%= live_flash(@flash, :error) %>
7 |
8 |
9 | <%= @inner_content %>
10 |
--------------------------------------------------------------------------------
/priv/email_templates/default-text-content.txt:
--------------------------------------------------------------------------------
1 | Hey {{ contact.first_name | default: "there" }}!
2 |
3 | You can write your campaign text here. This is a plain-text campaign, so no HTML!
4 |
5 | You can use the Liquid templating language (https://shopify.github.io/liquid/) here.
6 |
7 | Please make sure to include the following in every campaign:
8 | {{ unsubscribe_link }}
9 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/clipboard-check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/office-building.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/save-as.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/sun.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/calculator.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/collection.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/fast-forward.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/filter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/globe-alt.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/music-note.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/rewind.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/server.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila_web/views/public_form_layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.PublicFormLayoutView do
2 | use KeilaWeb, :view
3 | use Phoenix.HTML
4 |
5 | def build_styles(styles) do
6 | styles
7 | |> Enum.filter(fn {_, value} -> not is_nil(value) end)
8 | |> Enum.map(fn {key, value} -> ~s{#{key}:#{value}!important} end)
9 | |> Enum.join(";")
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chat.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/map.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/photograph.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/shopping-cart.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/js/hooks/remember-unsaved.js:
--------------------------------------------------------------------------------
1 | let unsaved = false
2 | export const RememberUnsaved = {
3 | mounted() {
4 | window.setUnsavedReminder = (enable) => {
5 | unsaved = enable
6 | }
7 | window.addEventListener("beforeunload", (e) => {
8 | if (unsaved) {
9 | e.preventDefault()
10 | e.returnValue = ""
11 | }
12 | })
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/keila_web/views/shared_sender_admin_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.SharedSenderAdminView do
2 | use KeilaWeb, :view
3 | alias KeilaWeb.Endpoint
4 |
5 | defp form_path(%{data: %{id: nil}}),
6 | do: Routes.shared_sender_admin_path(Endpoint, :create)
7 |
8 | defp form_path(%{data: %{id: id}}),
9 | do: Routes.shared_sender_admin_path(Endpoint, :update, id)
10 | end
11 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chat-alt-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/clipboard-copy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cube-transparent.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document-search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/support.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210104175000_root_group.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.RootGroup do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create index("groups", ["(parent_id is NULL)"],
6 | using: "btree",
7 | where: "(parent_id IS NULL)",
8 | name: :root_group,
9 | unique: true
10 | )
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/clipboard-list.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/currency-euro.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/hand.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/question-mark-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/receipt-tax.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/scissors.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/document-duplicate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/printer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila/auth/schemas/role_permission.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Auth.RolePermission do
2 | use Keila.Schema, prefix: "arp"
3 |
4 | schema "role_permissions" do
5 | belongs_to(:role, Keila.Auth.Role, type: Keila.Auth.Role.Id)
6 | belongs_to(:permission, Keila.Auth.Permission, type: Keila.Auth.Permission.Id)
7 | field(:is_inherited, :boolean)
8 |
9 | timestamps()
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250407180947_add_account_data.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddAccountData do
2 | use Ecto.Migration
3 | require Keila
4 |
5 | Keila.if_cloud do
6 | Code.ensure_loaded!(KeilaCloud.Migrations.AddAccountData)
7 | defdelegate change(), to: KeilaCloud.Migrations.AddAccountData
8 | else
9 | def change do
10 | :ok
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/briefcase.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chart-pie.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/currency-dollar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/stop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/keila/mailings/deliver_scheduled_campaigns_worker.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Mailings.DeliverScheduledCampaignsWorker do
2 | use Oban.Worker, queue: :mailer_scheduler
3 | alias Keila.Mailings
4 |
5 | def perform(%Oban.Job{}) do
6 | Mailings.get_campaigns_to_be_delivered(DateTime.utc_now())
7 | |> Enum.each(fn c ->
8 | Mailings.deliver_campaign_async(c.id)
9 | end)
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/bell.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/volume-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20231203235509_add_form_double_opt_in_settings.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddFormDoubleOptInSettings do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("contacts_forms") do
6 | add :sender_id, references("mailings_senders", on_delete: :nilify_all)
7 | add :template_id, references("templates", on_delete: :nilify_all)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/inbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/extra/keila_cloud_web/views/cloud_admin_view.ex:
--------------------------------------------------------------------------------
1 | require Keila
2 |
3 | Keila.if_cloud do
4 | defmodule KeilaWeb.CloudAdminView do
5 | use Phoenix.View,
6 | root: "extra/keila_cloud_web/templates",
7 | namespace: KeilaWeb
8 |
9 | use Phoenix.HTML
10 | import Phoenix.LiveView.Helpers
11 |
12 | import KeilaWeb.Gettext
13 | alias KeilaWeb.Router.Helpers, as: Routes
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/keila/auth/schemas/permission.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Auth.Permission do
2 | use Keila.Schema, prefix: "ap"
3 |
4 | schema "permissions" do
5 | field(:name, :string)
6 |
7 | timestamps()
8 | end
9 |
10 | @spec changeset(t(), Ecto.Changeset.data()) :: Ecto.Changeset.t(t())
11 | def changeset(struct \\ %__MODULE__{}, params) do
12 | struct
13 | |> cast(params, [:name])
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/chart-bar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/color-swatch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/globe.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/shield-check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/template.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/x-twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/keila/billing/schemas/plan.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Billing.Plan do
2 | defstruct [
3 | :paddle_id,
4 | :name,
5 | :monthly_credits,
6 | :is_active
7 | ]
8 |
9 | def new(paddle_id, name, monthly_credits, active?) do
10 | %__MODULE__{
11 | paddle_id: paddle_id,
12 | name: name,
13 | monthly_credits: monthly_credits,
14 | is_active: active?
15 | }
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/speakerphone.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/view-boards.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--list_items.liquid:
--------------------------------------------------------------------------------
1 | {% if style == "unordered" %}{% else %}{% endif %}
2 | {% for item in items %}
3 |
4 | {{ item.content }}
5 | {% if item.items %}
6 | {% render "block--list_items", items: item.items, style: style %}
7 | {% endif %}
8 |
9 | {% endfor %}
10 | {% if style == "unordered" %} {% else %}{% endif %}
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20241117001430_create_instance.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.CreateInstance do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table("instance") do
6 | add :available_updates, :jsonb, default: "[]"
7 | end
8 |
9 | execute "CREATE UNIQUE INDEX instance_singleton ON instance ((true));", ""
10 | execute "INSERT INTO instance DEFAULT VALUES;", ""
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/exclamation-triangle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/inbox-in.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/location-marker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/shield-exclamation.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/twitch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/light-bulb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/mail-open.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/phone.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/extra/keila_cloud_web/views/cloud_sender_view.ex:
--------------------------------------------------------------------------------
1 | require Keila
2 |
3 | Keila.if_cloud do
4 | defmodule KeilaWeb.CloudSenderView do
5 | use Phoenix.View,
6 | root: "extra/keila_cloud_web/templates",
7 | namespace: KeilaWeb
8 |
9 | use Phoenix.HTML
10 | import Phoenix.LiveView.Helpers
11 | import KeilaWeb.Gettext
12 | import KeilaWeb.ErrorHelpers
13 | import KeilaWeb.IconHelper
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/keila_web/api/validators/api_contact_filter_validator.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.Api.ContactFilterValidator do
2 | @moduledoc """
3 | OpenApiSpex cast module for casting JSON string to Map.
4 | """
5 |
6 | def cast(ctx = %{value: value}) do
7 | with {:ok, json} <- Jason.decode(value) do
8 | {:ok, json}
9 | else
10 | _ -> OpenApiSpex.Cast.error(ctx, {:invalid_type, :object})
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/identification.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/view-grid-add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/assets/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | darkMode: false, // or 'media' or 'class'
3 | content: ["./js/**/*.js", "./css/**/*.*css", "../lib/*_web/**/*.*ex", "../extra/**/*_web/**/*.*ex"],
4 | theme: {
5 | extend: {
6 | colors: {
7 | gray: {
8 | 950: "#0F131A"
9 | }
10 | }
11 | }
12 | },
13 | plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")]
14 | }
15 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/thumb-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210121100329_add_oban_jobs_table.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddObanJobsTable do
2 | use Ecto.Migration
3 |
4 | def up do
5 | Oban.Migrations.up()
6 | end
7 |
8 | # We specify `version: 1` in `down`, ensuring that we'll roll all the way back down if
9 | # necessary, regardless of which version we've migrated `up` to.
10 | def down do
11 | Oban.Migrations.down(version: 1)
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/share.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/eye.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/thumb-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/variable.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/tiktok.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/auth/404.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= dgettext("auth", "That didn’t work :-(") %>
5 |
6 |
7 | <%= dgettext("auth", "It seems like your link has expired or is invalid.") %>
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20240809143953_add_recipient_queued_at.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddRecipientQueuedAt do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_recipients") do
6 | add :queued_at, :utc_datetime
7 | end
8 |
9 | execute(&execute_up/0, fn -> :ok end)
10 | end
11 |
12 | defp execute_up() do
13 | repo().query!("UPDATE mailings_recipients SET queued_at=inserted_at")
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/puzzle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/book-open.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/phone-incoming.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/phone-outgoing.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/keila_web/helpers/hcaptcha_test.exs:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.CaptchaTest do
2 | use ExUnit.Case
3 | import KeilaWeb.Captcha
4 |
5 | @valid_hcaptcha "10000000-aaaa-bbbb-cccc-000000000001"
6 | @invalid_hcaptcha "00000000-aaaa-bbbb-cccc-000000000001"
7 |
8 | test "validate captchas" do
9 | assert true == captcha_valid?(@valid_hcaptcha)
10 | assert false == captcha_valid?(@invalid_hcaptcha)
11 | assert false == captcha_valid?("")
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/auth/register_success.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= dgettext("auth", "Check your inbox!") %>
5 |
6 |
7 | <%= dgettext("auth", "We’ve sent you a verification link to %{email}.", email: @user.email) %>
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/beaker.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/phone-missed-call.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/view-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila/mailings/sender_adapters/local.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Mailings.SenderAdapters.Local do
2 | use Keila.Mailings.SenderAdapters.Adapter
3 |
4 | @impl true
5 | def name, do: "local"
6 |
7 | @impl true
8 | def schema_fields do
9 | []
10 | end
11 |
12 | @impl true
13 | def changeset(changeset, _params) do
14 | changeset
15 | end
16 |
17 | @impl true
18 | def to_swoosh_config(_struct) do
19 | [adapter: Swoosh.Adapters.Local]
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210816142200_default_contact_status.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.DefaultContactStatus do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("contacts") do
6 | modify :status, :smallint, default: 0, null: false
7 | end
8 |
9 | execute(&execute_up/0, fn -> :ok end)
10 | end
11 |
12 | defp execute_up() do
13 | repo().query!(
14 | "UPDATE contacts SET status=$1",
15 | [0]
16 | )
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/qrcode.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/status-offline.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/user-group.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/volume-off.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/keila_web/helpers/instance_info_plug.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.InstanceInfoPlug do
2 | @behaviour Plug
3 | import Plug.Conn
4 |
5 | @impl true
6 | def call(conn, _opts) do
7 | if conn.assigns[:is_admin?] do
8 | updates_available? = Keila.Instance.updates_available?()
9 |
10 | conn
11 | |> assign(:instance_updates_available?, updates_available?)
12 | else
13 | conn
14 | end
15 | end
16 |
17 | @impl true
18 | def init(opts), do: opts
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210428165000_shared_senders.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.SharedSenders do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table("mailings_shared_senders") do
6 | add :name, :string, null: false
7 | add :config, :jsonb
8 |
9 | timestamps()
10 | end
11 |
12 | alter table("mailings_senders") do
13 | add :shared_sender_id, references("mailings_shared_senders", on_delete: :delete_all)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/camera.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/finger-print.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/keila_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ErrorViewTest do
2 | use KeilaWeb.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(KeilaWeb.ErrorView, "404.html", []) == "Not Found"
9 | end
10 |
11 | test "renders 500.html" do
12 | assert render_to_string(KeilaWeb.ErrorView, "500.html", []) == "Internal Server Error"
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210222154306_add_deliver_at_to_campaigns.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddDeliverAtToCampaigns do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_campaigns") do
6 | add :scheduled_for, :utc_datetime
7 | end
8 |
9 | create index("mailings_campaigns", [:scheduled_for, :sent_at],
10 | where: "scheduled_for is not null and sent_at is null",
11 | name: :scheduled_campaigns
12 | )
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/cake.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/fire.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/eye-off.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220114154000_add_campaign_custom_data.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddCampaignCustomData do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_campaigns") do
6 | add :data, :jsonb
7 | end
8 |
9 | create constraint("mailings_campaigns", :max_data_size,
10 | check: "pg_column_size(data) <= 32000"
11 | )
12 |
13 | create constraint("contacts", :max_data_size, check: "pg_column_size(data) <= 8000")
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/discourse.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/auth/activate_resend_success.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= dgettext("auth", "Check your inbox!") %>
5 |
6 |
7 | <%= dgettext("auth", "We’ve resent your verification link to %{email}.",
8 | email: @current_user.email
9 | ) %>
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20230120223000_add_segment_default_filter.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddSegmentDefaultFilter do
2 | use Ecto.Migration
3 |
4 | def change do
5 | execute(&execute_up/0, &execute_down/0)
6 | end
7 |
8 | defp execute_up() do
9 | repo().query!("UPDATE contacts_segments SET filter='{}' WHERE filter IS NULL")
10 |
11 | alter table("contacts_segments") do
12 | modify :filter, :jsonb, default: %{}
13 | end
14 | end
15 |
16 | defp execute_down(), do: :ok
17 | end
18 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/keila/mailings/schemas/campaign_settings.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Mailings.Campaign.Settings do
2 | use Ecto.Schema
3 | import Ecto.Changeset
4 |
5 | embedded_schema do
6 | field(:type, Ecto.Enum, values: [:text, :markdown, :block, :mjml])
7 | field(:enable_wysiwyg, :boolean, default: true)
8 | field(:do_not_track, :boolean, default: false)
9 | end
10 |
11 | def changeset(struct \\ %__MODULE__{}, params) do
12 | struct
13 | |> cast(params, [:type, :enable_wysiwyg, :do_not_track])
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/keila_web/helpers/meta/meta_plug.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.Meta.Plug do
2 | @moduledoc """
3 | Simple Plug for holding page meta information (title,
4 | description, etc).
5 |
6 | Use with helper functions from `KeilaWeb.Meta`.
7 | """
8 | import Plug.Conn
9 |
10 | @spec init(term()) :: term()
11 | def init(_opts) do
12 | nil
13 | end
14 |
15 | @spec call(Plug.Conn.t(), term) :: Plug.Conn.t()
16 | def call(conn, _opts) do
17 | conn
18 | |> put_private(:keila_meta, %{})
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/keila/accounts/schemas/account.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Accounts.Account do
2 | use Keila.Schema, prefix: "acc"
3 |
4 | schema "accounts" do
5 | belongs_to(:parent, __MODULE__, type: __MODULE__.Id)
6 | belongs_to(:group, Keila.Auth.Group, type: Keila.Auth.Group.Id)
7 |
8 | Keila.if_cloud do
9 | use KeilaCloud.Accounts.Account
10 | end
11 |
12 | timestamps()
13 | end
14 |
15 | def changeset(struct \\ %__MODULE__{}, params) do
16 | struct
17 | |> cast(params, [:group_id])
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20211116040700_segmentations.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.Segments do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table("contacts_segments") do
6 | add :project_id, references("projects", on_delete: :delete_all)
7 | add :name, :string
8 | add :filter, :jsonb
9 |
10 | timestamps()
11 | end
12 |
13 | alter table("mailings_campaigns") do
14 | add :segment_id, references("contacts_segments", on_delete: :nilify_all)
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/list-unordered.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210630205000_subscription_upserts.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.SubscriptionUpserts do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table("billing_subscriptions") do
6 | modify :update_url, :string, null: true
7 | modify :cancel_url, :string, null: true
8 | end
9 | end
10 |
11 | def down do
12 | alter table("billing_subscriptions") do
13 | modify :update_url, :string, null: false
14 | modify :cancel_url, :string, null: false
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/.zed/settings.json:
--------------------------------------------------------------------------------
1 | // Folder-specific settings
2 | //
3 | // For a full list of overridable settings, and general information on folder-specific settings,
4 | // see the documentation: https://zed.dev/docs/configuring-zed#settings-files
5 | {
6 | "languages": {
7 | "JavaScript": {
8 | "formatter": {
9 | "external": {
10 | "command": "dprint",
11 | "arguments": ["fmt", "--stdin", "{buffer_path}"]
12 | }
13 | }
14 | },
15 | "SCSS": {
16 | "tab_size": 2
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/sender/verification_failure.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= gettext("Sender verification not successful.") %>
5 |
6 |
7 | <%= gettext(
8 | "There was an error when trying to verify your sender. Please try again or contact an administrator."
9 | ) %>
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/keila/accounts/schemas/credit.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Accounts.CreditTransaction do
2 | use Keila.Schema, prefix: "acc"
3 |
4 | schema "accounts_credit_transactions" do
5 | field :amount, :integer
6 | field :expires_at, :utc_datetime
7 |
8 | belongs_to(:account, Keila.Accounts.Account, type: Keila.Accounts.Account.Id)
9 |
10 | timestamps(updated_at: false)
11 | end
12 |
13 | def changeset(struct \\ %__MODULE__{}, params) do
14 | struct
15 | |> cast(params, [:amount, :expires_at, :account_id])
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/auth/reset_success.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= dgettext("auth", "Check your inbox!") %>
5 |
6 |
7 | <%= dgettext(
8 | "auth",
9 | "If you have an account, we’ve sent you a verification link to %{email}.",
10 | email: @email
11 | ) %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lib/keila/tracking/schemas/link.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Tracking.Link do
2 | use Keila.Schema, prefix: "tl"
3 |
4 | schema "tracking_links" do
5 | field :url, :string
6 | belongs_to(:campaign, Keila.Mailings.Campaign, type: Keila.Mailings.Campaign.Id)
7 | has_many(:clicks, Keila.Tracking.Click)
8 |
9 | timestamps(updated_at: false)
10 | end
11 |
12 | def changeset(struct \\ %__MODULE__{}, params) do
13 | struct
14 | |> cast(params, [:url, :campaign_id])
15 | |> validate_required([:url, :campaign_id])
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250220063317_add_accounts_credit_transactions_on_delete.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddAccountsCreditTransactionsOnDelete do
2 | use Ecto.Migration
3 |
4 | def up do
5 | drop constraint(
6 | "accounts_credit_transactions",
7 | "accounts_credit_transactions_account_id_fkey"
8 | )
9 |
10 | alter table("accounts_credit_transactions") do
11 | modify :account_id, references("accounts", on_delete: :delete_all)
12 | end
13 | end
14 |
15 | def down do
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/keila/tracking/schemas/click.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Tracking.Click do
2 | use Keila.Schema
3 |
4 | schema "tracking_clicks" do
5 | belongs_to(:link, Keila.Tracking.Link, type: Keila.Tracking.Link.Id)
6 | belongs_to(:recipient, Keila.Mailings.Recipient, type: Keila.Mailings.Recipient.Id)
7 |
8 | timestamps(updated_at: false)
9 | end
10 |
11 | def changeset(struct \\ %__MODULE__{}, params) do
12 | struct
13 | |> cast(params, [:link_id, :recipient_id])
14 | |> validate_required([:link_id, :recipient_id])
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/truck.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/keila/mailings/sender_adapters/shared_local.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Mailings.SenderAdapters.Shared.Local do
2 | use Keila.Mailings.SenderAdapters.Adapter
3 |
4 | @impl true
5 | def name, do: "shared_local"
6 |
7 | @impl true
8 | def schema_fields do
9 | []
10 | end
11 |
12 | @impl true
13 | def changeset(changeset, _params) do
14 | changeset
15 | end
16 |
17 | @impl true
18 | def to_swoosh_config(%{shared_sender: shared_sender}) do
19 | Keila.Mailings.SenderAdapters.Local.to_swoosh_config(shared_sender)
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/star.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210311225200_templates.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.Templates do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table("templates") do
6 | add :project_id, references("projects", on_delete: :delete_all)
7 |
8 | add :name, :text
9 | add :body, :text
10 | add :styles, :text
11 | add :assigns, :jsonb
12 |
13 | timestamps()
14 | end
15 |
16 | alter table("mailings_campaigns") do
17 | add :template_id, references("templates", on_delete: :nilify_all)
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20230604141200_add_contacts_form_params_table.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddContactsFormParams do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table("contacts_form_params") do
6 | add :params, :jsonb
7 |
8 | add :form_id, references("contacts_forms", on_delete: :delete_all)
9 | add :expires_at, :utc_datetime
10 | timestamps()
11 | end
12 |
13 | create constraint("contacts_form_params", :max_data_size,
14 | check: "pg_column_size(params) <= 8000"
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/dprint.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript": {
3 | "semiColons": "asi",
4 | "trailingCommas": "never"
5 | },
6 | "json": {},
7 | "includes": ["**/*.{ts,tsx,js,jsx,cjs,mjs,json}"],
8 | "excludes": [
9 | "**/node_modules",
10 | "**/*-lock.json",
11 | "**/dist",
12 | "deps",
13 | "assets/static/vendor",
14 | "priv/vendor",
15 | "priv/static",
16 | "**/priv/cldr",
17 | "**/.elixir_ls"
18 | ],
19 | "plugins": [
20 | "https://plugins.dprint.dev/typescript-0.80.2.wasm",
21 | "https://plugins.dprint.dev/json-0.17.0.wasm"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/lib/keila_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ErrorView do
2 | use KeilaWeb, :view
3 |
4 | # If you want to customize a particular status code
5 | # for a certain format, you may uncomment below.
6 | # def render("500.html", _assigns) do
7 | # "Internal Server Error"
8 | # end
9 |
10 | # By default, Phoenix returns the status message from
11 | # the template name. For example, "404.html" becomes
12 | # "Not Found".
13 | def template_not_found(template, _assigns) do
14 | Phoenix.Controller.status_message_from_template(template)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/block.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/extra/keila_cloud_web/controllers/cloud_account_controller.ex:
--------------------------------------------------------------------------------
1 | require Keila
2 |
3 | Keila.if_cloud do
4 | defmodule KeilaWeb.CloudAccountController do
5 | use KeilaWeb, :controller
6 | import Phoenix.LiveView.Controller
7 |
8 | def onboarding(conn, _) do
9 | live_render(conn, KeilaWeb.CloudAccountOnboardingLive,
10 | session: %{
11 | "current_account" => conn.assigns.current_account,
12 | "current_user" => conn.assigns.current_user,
13 | "locale" => Gettext.get_locale()
14 | }
15 | )
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20221227145500_add_recipient_states.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddRecipientStates do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("mailings_recipients") do
6 | add :soft_bounce_received_at, :utc_datetime
7 | add :hard_bounce_received_at, :utc_datetime
8 | add :complaint_received_at, :utc_datetime
9 | add :unsubscribed_at, :utc_datetime
10 | end
11 |
12 | alter table("contacts_events") do
13 | add :recipient_id, references("mailings_recipients", on_delete: :nilify_all)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/vendor/font-awesome/spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/youtube.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220523235000_add_files.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddFiles do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table("files", primary_key: false) do
6 | add :uuid, :binary, length: 16, primary_key: true
7 | add :filename, :string
8 | add :type, :string
9 | add :size, :integer
10 | add :sha256, :binary, length: 32
11 | add :adapter, :string, length: 16
12 | add :adapter_data, :jsonb
13 |
14 | add :project_id, references("projects", on_delete: :nilify_all)
15 |
16 | timestamps()
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/assets/js/hooks/file_manager.js:
--------------------------------------------------------------------------------
1 | const FileManager = {
2 | mounted() {
3 | this.handleEvent("remove_file", ({ id, src }) => {
4 | this.el.dispatchEvent(
5 | new CustomEvent("x-file-removed", {
6 | detail: { id, src },
7 | bubbles: true
8 | })
9 | )
10 | })
11 |
12 | this.handleEvent("file_in_use", ({ campaigns }) => {
13 | this.el.dispatchEvent(
14 | new CustomEvent("x-file-in-use", {
15 | detail: { campaigns },
16 | bubbles: true
17 | })
18 | )
19 | })
20 | }
21 | }
22 |
23 | export { FileManager }
24 |
--------------------------------------------------------------------------------
/lib/keila_web/views/campaign_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.CampaignView do
2 | use KeilaWeb, :view
3 | require Keila
4 | import Ecto.Changeset, only: [get_field: 2]
5 |
6 | def plain_text_preview(text) do
7 | """
8 |
9 |
10 |
11 |
12 |
13 | #{text}
14 |
15 |
16 |
17 | """
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220905125000_emails_to_citext.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.EmailsToCitext do
2 | use Ecto.Migration
3 |
4 | def up do
5 | execute "CREATE EXTENSION IF NOT EXISTS citext"
6 |
7 | alter table("users") do
8 | modify :email, :citext, null: false
9 | end
10 |
11 | alter table("contacts") do
12 | modify :email, :citext
13 | end
14 | end
15 |
16 | def down do
17 | alter table("users") do
18 | modify :email, :string, null: false
19 | end
20 |
21 | alter table("contacts") do
22 | modify :email, :string
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/academic-cap.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/priv/email_templates/hybrid/_block--button.liquid:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/xing.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/priv/vendor/font-awesome/align-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/priv/vendor/hero-icons-outline/badge-check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/auth/registration_disabled.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | <%= gettext("Registration disabled.") %>
8 |
9 |
10 | <%= gettext("Please contact an administrator.") %>
11 |
12 |
13 | <%= gettext("Already have an account?") %>
14 | <%= gettext("Sign in.") %>
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/priv/vendor/font-awesome/align-justify.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/priv/vendor/font-awesome/align-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/lib/keila/instance/update_cron_worker.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Instance.UpdateCronWorker do
2 | use Oban.Worker,
3 | queue: :updater,
4 | unique: [
5 | period: :infinity,
6 | states: [:available, :scheduled, :executing]
7 | ]
8 |
9 | use Keila.Repo
10 | alias Keila.Instance
11 |
12 | @impl true
13 | def perform(_job) do
14 | if Instance.update_checks_enabled?() do
15 | releases = Instance.fetch_updates()
16 |
17 | Instance.get_instance()
18 | |> change()
19 | |> put_embed(:available_updates, releases)
20 | |> Repo.update()
21 | else
22 | :ok
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/priv/vendor/font-awesome/align-center.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/keila_web/api/views/api_sender_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ApiSenderView do
2 | use KeilaWeb, :view
3 | alias Keila.Pagination
4 |
5 | def render("senders.json", %{senders: senders = %Pagination{}}) do
6 | %{
7 | "meta" => %{
8 | "page" => senders.page,
9 | "page_count" => senders.page_count,
10 | "count" => senders.count
11 | },
12 | "data" => Enum.map(senders.data, &sender_data/1)
13 | }
14 | end
15 |
16 | @properties [:id, :name, :from_email, :from_name, :reply_to_email, :reply_to_name]
17 | defp sender_data(sender) do
18 | sender
19 | |> Map.take(@properties)
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/extra/keila_cloud_web/templates/cloud_account/_completed.html.heex:
--------------------------------------------------------------------------------
1 | Thank you!
2 | <%= dgettext_md("cloud", """
3 | We will now perform a manual review of your data. This usually doesn't take longer than one working day.
4 |
5 | In the meantime, you can continue to use Keila. Sending emails will be enabled for your account
6 | once the review has been completed.
7 | """) %>
8 |
9 |
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20210319145300_form_timestamps.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.FormTimestamps do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("contacts_forms") do
6 | timestamps(null: true)
7 | end
8 |
9 | execute(&execute_up/0, &execute_down/0)
10 | end
11 |
12 | defp execute_up() do
13 | repo().query!("UPDATE contacts_forms SET updated_at=NOW(), inserted_at=NOW()")
14 |
15 | alter table("contacts_forms") do
16 | modify :inserted_at, :naive_datetime, null: false
17 | modify :updated_at, :naive_datetime, null: false
18 | end
19 | end
20 |
21 | defp execute_down(), do: :ok
22 | end
23 |
--------------------------------------------------------------------------------
/lib/keila/auth/schemas/role.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Auth.Role do
2 | use Keila.Schema, prefix: "ar"
3 |
4 | schema "roles" do
5 | field(:name, :string)
6 | has_many(:role_permissions, Keila.Auth.RolePermission)
7 |
8 | belongs_to(:parent, __MODULE__,
9 | foreign_key: :parent_id,
10 | references: :id,
11 | type: Keila.Auth.Role.Id
12 | )
13 |
14 | timestamps()
15 | end
16 |
17 | @spec changeset(t(), Ecto.Changeset.data()) :: Ecto.Changeset.t(t())
18 | def changeset(struct \\ %__MODULE__{}, params) do
19 | struct
20 | |> cast(params, [:name, :parent_id])
21 | |> foreign_key_constraint(:parent_id)
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/bluesky.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/priv/vendor/social-icons/linkedin.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/sender/verification_success.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= gettext("Sender verified!") %>
5 |
6 |
7 | <%= gettext(
8 | "Thank you for verifying your sender. You can now start sending emails with Keila."
9 | ) %>
10 |
11 |
12 |
13 | <%= gettext("Back to your project") %>
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/assets/css/_header.scss:
--------------------------------------------------------------------------------
1 |
2 | #main-menu {
3 | #main-menu-toggle:checked {
4 | &~* #show-main-menu {
5 | display: none;
6 | }
7 | &~ nav {
8 | @apply flex;
9 | }
10 | }
11 |
12 | #main-menu-toggle:not(:checked) ~ * #hide-main-menu {
13 | display: none;
14 | }
15 | }
16 |
17 | .menu-link {
18 | @apply flex gap-2 items-center text-white hover:bg-emerald-800 hover:text-white px-3 py-2 rounded-md text-sm font-medium;
19 |
20 | &.menu-link--active {
21 | @apply bg-emerald-800 text-white;
22 | }
23 |
24 | &.sml-4.menu-link--active {
25 | @apply bg-emerald-900 text-white;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/extra/keila_cloud/schemas/account.ex:
--------------------------------------------------------------------------------
1 | require Keila
2 |
3 | Keila.if_cloud do
4 | defmodule KeilaCloud.Accounts.Account do
5 | defmacro __using__(_opts) do
6 | quote do
7 | embeds_one(:contact_data, KeilaCloud.Accounts.Account.ContactData)
8 |
9 | embeds_one(
10 | :onboarding_review_data,
11 | KeilaCloud.Accounts.Account.OnboardingReviewData
12 | )
13 |
14 | field :status, Ecto.Enum,
15 | values: [
16 | default: 0,
17 | active: 1,
18 | suspended: -1,
19 | onboarding_required: 10,
20 | under_review: 11
21 | ]
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/assets/js/campaign-editors/block/blocks/separator.js:
--------------------------------------------------------------------------------
1 | export default class Separator {
2 | constructor(_data) {
3 | this.data = {}
4 | }
5 |
6 | render() {
7 | const el = document.createElement("div")
8 | el.appendChild(document.createElement("hr"))
9 |
10 | return el
11 | }
12 |
13 | save(_blockContent) {
14 | return {}
15 | }
16 |
17 | static get contentless() {
18 | return true
19 | }
20 |
21 | static get toolbox() {
22 | return {
23 | title: document.querySelector("#block-container-assets .editor-separator-title").innerText,
24 | icon: document.querySelector("#block-container-assets .icon-horizontal-rule").innerHTML
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/keila/mailings/sender_adapters/sendgrid.ex:
--------------------------------------------------------------------------------
1 | defmodule Keila.Mailings.SenderAdapters.Sendgrid do
2 | use Keila.Mailings.SenderAdapters.Adapter
3 |
4 | @impl true
5 | def name, do: "sendgrid"
6 |
7 | @impl true
8 | def schema_fields do
9 | [
10 | sendgrid_api_key: :string
11 | ]
12 | end
13 |
14 | @impl true
15 | def changeset(changeset, params) do
16 | changeset
17 | |> cast(params, [:sendgrid_api_key])
18 | |> validate_required([:sendgrid_api_key])
19 | end
20 |
21 | @impl true
22 | def to_swoosh_config(%{config: config}) do
23 | [
24 | adapter: Swoosh.Adapters.Sendgrid,
25 | api_key: config.sendgrid_api_key
26 | ]
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/auth/activate_success.html.heex:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/project/_empty_state_campaigns.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= render_icon(:at_symbol) %>
3 |
4 |
21 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/public_form/double_opt_in_failure.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <%= render_h1(@form) %>
3 |
4 | <%= if @form.settings.failure_text && @form.settings.failure_text != "" do %>
5 | <%= @form.settings.failure_text %>
6 | <% else %>
7 | <%= gettext("This confirmation link has expired or has already been used.") %>
8 | <% end %>
9 |
10 |
11 | <%= gettext("Please submit the form again to receive a new confirmation link.") %>
12 |
13 |
14 |
15 | <%= gettext("Back to form") %>
16 |
17 |
18 | <%= render_fine_print(@form) %>
19 |
20 |
--------------------------------------------------------------------------------
/priv/vendor/keila-icons/mjml.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/keila_web/templates/auth/reset_change_password_success.html.heex:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/test/keila_web/controllers/auth_controller_login_disabled_test.exs:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.AuthControllerDisabledTest do
2 | use KeilaWeb.ConnCase, async: false
3 |
4 | setup do
5 | Application.put_env(:keila, :registration_disabled, true)
6 |
7 | on_exit(fn ->
8 | Application.put_env(:keila, :registration_disabled, false)
9 | end)
10 | end
11 |
12 | @tag :auth_controller
13 | test "Registration can be disabled", %{conn: conn} do
14 | conn = get(conn, Routes.auth_path(conn, :register))
15 | assert html_response(conn, 200) =~ "Registration disabled"
16 |
17 | conn = post(conn, Routes.auth_path(conn, :post_register))
18 | assert html_response(conn, 200) =~ "Registration disabled"
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/.github/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/lib/keila_web/api/plugs/api_authorization_plug.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.Api.Plugs.Authorization do
2 | @behaviour Plug
3 | import Plug.Conn
4 | alias Keila.Auth
5 | alias Keila.Projects
6 |
7 | def init(_), do: nil
8 |
9 | def call(conn, _) do
10 | with ["Bearer " <> api_key] <- get_req_header(conn, "authorization"),
11 | %Auth.Token{data: %{"project_id" => project_id}, user_id: user_id} <-
12 | Auth.find_api_key(api_key),
13 | project = %Projects.Project{} <-
14 | Projects.get_user_project(user_id, project_id) do
15 | conn
16 | |> assign(:current_project, project)
17 | else
18 | _ -> conn |> KeilaWeb.Api.Errors.send_403() |> halt()
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/keila_web/views/contact_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ContactView do
2 | use KeilaWeb, :view
3 | use Phoenix.HTML
4 |
5 | def table_sort_button(assigns) do
6 | active? = assigns[:current_key] == assigns[:key]
7 |
8 | ~H"""
9 |
15 | ((active? && " bg-gray-600 hover:bg-gray-700") || "")
16 | }
17 | >
18 | <%= if active? and @current_order == -1,
19 | do: render_icon(:chevron_up),
20 | else: render_icon(:chevron_down) %>
21 |
22 | """
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/keila_web/helpers/project/project_plug.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ProjectPlug do
2 | @moduledoc """
3 | This Plug adds a `current_project` assign from the `:project_id` path param.
4 |
5 | Halts the pipeline with 404 if the current user is not authorized to access
6 | the project or if the project doesn‘t exist.
7 | """
8 | import Plug.Conn
9 | alias Keila.Projects
10 |
11 | def init(_), do: []
12 |
13 | def call(conn, _) do
14 | project_id = conn.path_params["project_id"]
15 | user = conn.assigns.current_user
16 |
17 | case Projects.get_user_project(user.id, project_id) do
18 | nil -> conn |> put_status(404) |> halt()
19 | project -> assign(conn, :current_project, project)
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250707201249_add_verified_from_email_to_senders.exs:
--------------------------------------------------------------------------------
1 | defmodule Keila.Repo.Migrations.AddVerifiedFromEmailToSenders do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table("mailings_senders") do
6 | add :verified_from_email, :string
7 | end
8 |
9 | # Data migration to set verified_from_email based on sender type
10 | execute """
11 | UPDATE mailings_senders
12 | SET verified_from_email = config->>'shared_ses_verification_requested_for'
13 | WHERE config->>'type' = 'shared_ses' AND config->>'shared_ses_verified_at' IS NOT NULL
14 | """
15 | end
16 |
17 | def down do
18 | alter table("mailings_senders") do
19 | remove :verified_from_email
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/keila_web/api/views/api_segment_view.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.ApiSegmentView do
2 | use KeilaWeb, :view
3 | alias Keila.Pagination
4 |
5 | def render("segments.json", %{segments: segments = %Pagination{}}) do
6 | %{
7 | "meta" => %{
8 | "page" => segments.page,
9 | "page_count" => segments.page_count,
10 | "count" => segments.count
11 | },
12 | "data" => Enum.map(segments.data, &segment_data/1)
13 | }
14 | end
15 |
16 | def render("segment.json", %{segment: segment}) do
17 | %{
18 | "data" => segment_data(segment)
19 | }
20 | end
21 |
22 | @properties [:id, :name, :filter]
23 | defp segment_data(segment) do
24 | segment
25 | |> Map.take(@properties)
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/keila_web/helpers/plain_text_json_parser/plain_text_json_parser.ex:
--------------------------------------------------------------------------------
1 | defmodule KeilaWeb.PlainTextJSONParser do
2 | @moduledoc """
3 | This Parser plug is used to parse requests with content-type `text/plain` as JSON.
4 |
5 | It’s necessary to use this plug when external applications (e.g. webhooks)
6 | use `text/plain` instead of `application/json`.
7 | """
8 |
9 | @behaviour Plug.Parsers
10 |
11 | @impl true
12 | def init(opts) do
13 | Plug.Parsers.JSON.init(opts)
14 | end
15 |
16 | @impl true
17 | def parse(conn, "text", "plain", params, opts) do
18 | Plug.Parsers.JSON.parse(conn, "application", "json", params, opts)
19 | end
20 |
21 | def parse(conn, _type, _subtype, _params, _opts) do
22 | {:next, conn}
23 | end
24 | end
25 |
--------------------------------------------------------------------------------