diff --git a/.env.example b/.env.example index b9df76019..71d354b02 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ export PHX_SERVER=true export PHX_HOST=localhost export PORT=4000 export SECRET_KEY_BASE=CHANGE_ME +# export LICENCE_KEY=lce_... export DATABASE_URL=postgresql://postgres:postgres@localhost/azimutt_dev # export DATABASE_IPV6=true diff --git a/INSTALL.md b/INSTALL.md index 50f5f3c78..48a4c9d9f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -128,6 +128,7 @@ These are the basic variables you will **need** to set up Azimutt: - `PHX_HOST` (required): host of the deployed website (ex: `localhost` or `azimutt.app`), it's used to build absolute urls - `PORT` (required): the port the server will listen to (ex: `4000`) - `SECRET_KEY_BASE` (required): the secret used for server encryption (cookies and others), should be at least 64 bytes and you probably want a random value for it +- `LICENCE_KEY` (optional): the licence key to unlock the pro features, contact us if you need one (contact@azimutt.app) - `DATABASE_URL` (required): the whole url to connect to your PostgreSQL database (ex: `postgresql://:@:/`) - `DATABASE_IPV6` (optional): if `true`, the database driver will use IPV6 - `DATABASE_POOL_SIZE` (optional, default: `10`): the database connection pool size diff --git a/backend/.credo.exs b/backend/.credo.exs index 6b0919177..15e59b6d1 100644 --- a/backend/.credo.exs +++ b/backend/.credo.exs @@ -109,7 +109,7 @@ {Credo.Check.Readability.RedundantBlankLines, []}, {Credo.Check.Readability.Semicolons, []}, {Credo.Check.Readability.SpaceAfterCommas, []}, - {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.StringSigils, [maximum_allowed_quotes: 4]}, {Credo.Check.Readability.TrailingBlankLine, []}, {Credo.Check.Readability.TrailingWhiteSpace, []}, {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, diff --git a/backend/config/config.exs b/backend/config/config.exs index 7abad4d77..60c276a27 100644 --- a/backend/config/config.exs +++ b/backend/config/config.exs @@ -18,13 +18,14 @@ config :azimutt, cli_url: "https://www.npmjs.com/package/azimutt", heroku_url: "https://elements.heroku.com/addons/azimutt", browser_extension_url: "https://chrome.google.com/webstore/detail/azimutt/bpifdkechgdibghkkpaioccoijeoebjf", - documentation_url: "https://docs.azimutt.app", - github_url: "https://github.com/azimuttapp/azimutt", - github_issues: "https://github.com/azimuttapp/azimutt/issues", - github_new_issue: "https://github.com/azimuttapp/azimutt/issues/new", - twitter_url: "https://twitter.com/azimuttapp", - linkedin_url: "https://www.linkedin.com/company/azimuttapp", - slack_url: "https://join.slack.com/t/azimutt/shared_invite/zt-1pumru3pj-iBKIq7f~7ADOfySuxuFA2Q", + azimutt_documentation: "https://docs.azimutt.app", + azimutt_email: "contact@azimutt.app", + azimutt_twitter: "https://twitter.com/azimuttapp", + azimutt_linkedin: "https://www.linkedin.com/company/azimuttapp", + azimutt_slack: "https://join.slack.com/t/azimutt/shared_invite/zt-1pumru3pj-iBKIq7f~7ADOfySuxuFA2Q", + azimutt_github: "https://github.com/azimuttapp/azimutt", + azimutt_github_issues: "https://github.com/azimuttapp/azimutt/issues", + azimutt_github_issues_new: "https://github.com/azimuttapp/azimutt/issues/new", pro_plan_seat_price: 13, free_plan_seats: 3, # MUST stay in sync with frontend/src/Conf.elm (`features`) diff --git a/backend/config/runtime.exs b/backend/config/runtime.exs index 17b2de445..fa6f326b5 100644 --- a/backend/config/runtime.exs +++ b/backend/config/runtime.exs @@ -23,6 +23,7 @@ global_organization = System.get_env("GLOBAL_ORGANIZATION") config :azimutt, host: host, + licence: System.get_env("LICENCE_KEY"), gateway_url: System.get_env("GATEWAY_URL") || "/api/v1/analyzer", skip_public_site: !(System.get_env("PUBLIC_SITE") == "true"), skip_onboarding_funnel: System.get_env("SKIP_ONBOARDING_FUNNEL") == "true", diff --git a/backend/lib/azimutt.ex b/backend/lib/azimutt.ex index 8d459a2cd..42c04d85c 100644 --- a/backend/lib/azimutt.ex +++ b/backend/lib/azimutt.ex @@ -23,6 +23,10 @@ defmodule Azimutt do Application.get_env(:azimutt, key, default) end + def set_config(key, value) when is_atom(key) do + Application.put_env(:azimutt, key, value) + end + def plans do # Next ones: Explore ($3), Expand ($13), Extend ($25) [ diff --git a/backend/lib/azimutt/application.ex b/backend/lib/azimutt/application.ex index abc06aa86..f14e3cd0e 100644 --- a/backend/lib/azimutt/application.ex +++ b/backend/lib/azimutt/application.ex @@ -32,7 +32,7 @@ defmodule Azimutt.Application do res = Supervisor.start_link(children, opts) check_global_organization() - CockpitSrv.boot_check() + CockpitSrv.on_boot() res end diff --git a/backend/lib/azimutt/organizations.ex b/backend/lib/azimutt/organizations.ex index 61828a95f..3ad2cb2a6 100644 --- a/backend/lib/azimutt/organizations.ex +++ b/backend/lib/azimutt/organizations.ex @@ -330,35 +330,37 @@ defmodule Azimutt.Organizations do end def get_organization_plan(%Organization{} = organization) do + plans = Azimutt.config(:instance_plans) || ["free"] + cond do - organization.clever_cloud_resource -> clever_cloud_plan(organization.clever_cloud_resource) - organization.heroku_resource -> heroku_plan(organization.heroku_resource) - organization.stripe_subscription_id && StripeSrv.stripe_configured?() -> stripe_plan(organization.stripe_subscription_id) - true -> default_plan() + organization.clever_cloud_resource -> clever_cloud_plan(plans, organization.clever_cloud_resource) + organization.heroku_resource -> heroku_plan(plans, organization.heroku_resource) + organization.stripe_subscription_id && StripeSrv.stripe_configured?() -> stripe_plan(plans, organization.stripe_subscription_id) + true -> default_plan(plans) end - |> Result.map(fn plan -> plan_overrides(organization, plan) end) + |> Result.map(fn plan -> plan_overrides(plans, organization, plan) end) end - defp clever_cloud_plan(%CleverCloud.Resource{} = resource) do - if resource.plan |> String.starts_with?("pro-") do + defp clever_cloud_plan(plans, %CleverCloud.Resource{} = resource) do + if resource.plan |> String.starts_with?("pro-") && plans |> Enum.member?("pro") do {:ok, OrganizationPlan.pro()} else {:ok, OrganizationPlan.free()} end end - defp heroku_plan(%Heroku.Resource{} = resource) do - if resource.plan |> String.starts_with?("pro-") || resource.plan == "test" do + defp heroku_plan(plans, %Heroku.Resource{} = resource) do + if (resource.plan |> String.starts_with?("pro-") || resource.plan == "test") && plans |> Enum.member?("pro") do {:ok, OrganizationPlan.pro()} else {:ok, OrganizationPlan.free()} end end - defp stripe_plan(subscription_id) do + defp stripe_plan(plans, subscription_id) do StripeSrv.get_subscription(subscription_id) |> Result.map(fn s -> - if s.status == "active" || s.status == "past_due" || s.status == "unpaid" do + if (s.status == "active" || s.status == "past_due" || s.status == "unpaid") && plans |> Enum.member?("pro") do OrganizationPlan.pro() else OrganizationPlan.free() @@ -366,15 +368,18 @@ defmodule Azimutt.Organizations do end) end - def default_plan do - case Azimutt.config(:organization_default_plan) do - "pro" -> {:ok, OrganizationPlan.pro()} - _ -> {:ok, OrganizationPlan.free()} + def default_plan(plans) do + plan = Azimutt.config(:organization_default_plan) + + if plan == "pro" && plans |> Enum.member?("pro") do + {:ok, OrganizationPlan.pro()} + else + {:ok, OrganizationPlan.free()} end end - defp plan_overrides(%Organization{} = organization, %OrganizationPlan{} = plan) do - if organization.data != nil do + defp plan_overrides(plans, %Organization{} = organization, %OrganizationPlan{} = plan) do + if organization.data != nil && plans |> Enum.member?("pro") do plan |> override_layouts(organization.data) |> override_memos(organization.data) diff --git a/backend/lib/azimutt/services/cockpit_srv.ex b/backend/lib/azimutt/services/cockpit_srv.ex index 30b20c032..6bad6744b 100644 --- a/backend/lib/azimutt/services/cockpit_srv.ex +++ b/backend/lib/azimutt/services/cockpit_srv.ex @@ -12,27 +12,47 @@ defmodule Azimutt.Services.CockpitSrv do alias Azimutt.Utils.Result alias Azimutt.Utils.Stringx - def boot_check do - # TODO: add code version - post("/api/check", %{ - instance: Azimutt.config(:host), - environment: Azimutt.config(:environment), - db: db_stats(), - config: instance_conf() - }) - + def on_boot do + check(true) Runner.start_link() end - def server_up do - post("/api/events", %{ + def check(startup) do + # TODO: add code version + post("/api/licences/check", %{ instance: Azimutt.config(:host), environment: Azimutt.config(:environment), - name: "server_up", - details: %{}, - entities: [], - createdAt: DateTime.utc_now() + licence: Azimutt.config(:licence), + startup: startup, + db: db_stats(), + config: instance_conf() }) + |> Result.fold( + fn _ -> + set_error_message( + "Unable to reach licence server, please make sure to allow access or contact us: #{Azimutt.config(:azimutt_email)}." + ) + end, + fn res -> + Azimutt.set_config(:instance_plans, res["plans"]) + + cond do + res["error"] != nil -> + set_error_message(res["error"]) + + res["warning"] != nil -> + set_warning_message(res["warning"]) + + res["status"] != 200 -> + set_warning_message( + "Licence server returned status #{res["status"]}, please contact us at #{Azimutt.config(:azimutt_email)}." + ) + + true -> + clear_message() + end + end + ) end def send_event(%Event{} = event) do @@ -66,7 +86,7 @@ defmodule Azimutt.Services.CockpitSrv do @impl true def handle_info(:work, state) do - CockpitSrv.server_up() + CockpitSrv.check(false) {:noreply, state} end end @@ -196,4 +216,19 @@ defmodule Azimutt.Services.CockpitSrv do updated_at: project.updated_at } end + + defp set_error_message(message) do + Azimutt.set_config(:instance_message_color, "red") + Azimutt.set_config(:instance_message, message) + end + + defp set_warning_message(message) do + Azimutt.set_config(:instance_message_color, "yellow") + Azimutt.set_config(:instance_message, message) + end + + defp clear_message do + Azimutt.set_config(:instance_message_color, nil) + Azimutt.set_config(:instance_message, nil) + end end diff --git a/backend/lib/azimutt/utils/markdown.ex b/backend/lib/azimutt/utils/markdown.ex index ca2e98204..dc8d596d8 100644 --- a/backend/lib/azimutt/utils/markdown.ex +++ b/backend/lib/azimutt/utils/markdown.ex @@ -18,7 +18,7 @@ defmodule Azimutt.Utils.Markdown do end def preprocess(content, path) do - github = "https://github.com/azimuttapp/azimutt" + github = Azimutt.config(:azimutt_github) content |> String.replace("{{base_link}}", base_link(path)) @@ -26,8 +26,8 @@ defmodule Azimutt.Utils.Markdown do |> String.replace("{{roadmap_link}}", "#{github}/projects/1") |> String.replace("{{issues_link}}", "#{github}/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22") |> String.replace("{{feedback_link}}", "#{github}/discussions") - |> String.replace("{{azimutt_twitter}}", "https://twitter.com/azimuttapp") - |> String.replace("{{azimutt_email}}", "contact@azimutt.app") + |> String.replace("{{azimutt_twitter}}", Azimutt.config(:azimutt_twitter)) + |> String.replace("{{azimutt_email}}", Azimutt.config(:azimutt_email)) end def base_link(path), do: path |> String.split("/") |> Enum.drop(2) |> Enum.take(2) |> Enum.map_join(fn p -> "/#{p}" end) diff --git a/backend/lib/azimutt_web/router.ex b/backend/lib/azimutt_web/router.ex index 120a9171f..2e43b4d1f 100644 --- a/backend/lib/azimutt_web/router.ex +++ b/backend/lib/azimutt_web/router.ex @@ -280,7 +280,7 @@ defmodule AzimuttWeb.Router do description: "API Documentation for Azimutt Backend", contact: %{ name: "Azimutt", - email: "contact@azimutt.app" + email: Azimutt.config(:azimutt_email) } }, consumes: ["application/json"], diff --git a/backend/lib/azimutt_web/templates/blog/_blog_footer.html.heex b/backend/lib/azimutt_web/templates/blog/_blog_footer.html.heex index 0df7ce571..d56ab5d05 100644 --- a/backend/lib/azimutt_web/templates/blog/_blog_footer.html.heex +++ b/backend/lib/azimutt_web/templates/blog/_blog_footer.html.heex @@ -7,17 +7,17 @@ diff --git a/backend/lib/azimutt_web/templates/gallery/index.html.heex b/backend/lib/azimutt_web/templates/gallery/index.html.heex index de930a9f8..a5d7c94eb 100644 --- a/backend/lib/azimutt_web/templates/gallery/index.html.heex +++ b/backend/lib/azimutt_web/templates/gallery/index.html.heex @@ -46,10 +46,10 @@ diff --git a/backend/lib/azimutt_web/templates/layout/_hello_comment.html.heex b/backend/lib/azimutt_web/templates/layout/_hello_comment.html.heex index 2cd4e60df..812fca6a8 100644 --- a/backend/lib/azimutt_web/templates/layout/_hello_comment.html.heex +++ b/backend/lib/azimutt_web/templates/layout/_hello_comment.html.heex @@ -1,5 +1,6 @@ - - - + + + + diff --git a/backend/lib/azimutt_web/templates/layout/_instance_message.html.heex b/backend/lib/azimutt_web/templates/layout/_instance_message.html.heex new file mode 100644 index 000000000..45c3cb979 --- /dev/null +++ b/backend/lib/azimutt_web/templates/layout/_instance_message.html.heex @@ -0,0 +1,5 @@ +<%= if Azimutt.config(:instance_message) do %> +
+

<%= raw(Azimutt.config(:instance_message)) %>

+
+<% end %> diff --git a/backend/lib/azimutt_web/templates/layout/root_organization.html.heex b/backend/lib/azimutt_web/templates/layout/root_organization.html.heex index f24d54c41..567c08dc7 100644 --- a/backend/lib/azimutt_web/templates/layout/root_organization.html.heex +++ b/backend/lib/azimutt_web/templates/layout/root_organization.html.heex @@ -5,6 +5,7 @@ <%= render "_hello_comment.html" %> + <%= render "_instance_message.html" %> <%= render "_email_confirm_banner.html", conn: @conn, current_user: @current_user %>
<%= render "_user_navbar.html", conn: @conn, current_user: @current_user %> diff --git a/backend/lib/azimutt_web/templates/layout/root_user_settings.html.heex b/backend/lib/azimutt_web/templates/layout/root_user_settings.html.heex index 60cd6b384..e09b69045 100644 --- a/backend/lib/azimutt_web/templates/layout/root_user_settings.html.heex +++ b/backend/lib/azimutt_web/templates/layout/root_user_settings.html.heex @@ -3,6 +3,7 @@ <%= render "_head.html", conn: @conn %> <%= render "_hello_comment.html" %> + <%= render "_instance_message.html" %> <%= render "_email_confirm_banner.html", conn: @conn, current_user: @current_user %>
<%= render "_user_navbar.html", conn: @conn, current_user: @current_user %> diff --git a/backend/lib/azimutt_web/templates/user_onboarding/community.html.heex b/backend/lib/azimutt_web/templates/user_onboarding/community.html.heex index b5cffa901..004072031 100644 --- a/backend/lib/azimutt_web/templates/user_onboarding/community.html.heex +++ b/backend/lib/azimutt_web/templates/user_onboarding/community.html.heex @@ -32,7 +32,7 @@
-
+
diff --git a/backend/lib/azimutt_web/templates/website/_footer.html.heex b/backend/lib/azimutt_web/templates/website/_footer.html.heex index d988aee31..f8ed9aa5b 100644 --- a/backend/lib/azimutt_web/templates/website/_footer.html.heex +++ b/backend/lib/azimutt_web/templates/website/_footer.html.heex @@ -11,19 +11,19 @@ We contribute 1% of our revenue to carbon removal.

- + GitHub - + Twitter - + LinkedIn
diff --git a/backend/lib/azimutt_web/templates/website/feature-support.html.heex b/backend/lib/azimutt_web/templates/website/feature-support.html.heex index 2b53cca8d..c3273998c 100644 --- a/backend/lib/azimutt_web/templates/website/feature-support.html.heex +++ b/backend/lib/azimutt_web/templates/website/feature-support.html.heex @@ -10,11 +10,11 @@

We believe user feedback is gold, so we made us very easy to reach. We answer within the day most of the time, fixing issues right away and even sometimes adding convenience features on the fly (thanks to Elm, making our code robust and easy to change!).

Reach at us on:

If you are still here, send us a hello, we love connecting with people interested in the same topic as us!

diff --git a/backend/lib/azimutt_web/templates/website/index.html.heex b/backend/lib/azimutt_web/templates/website/index.html.heex index c352f085c..39710803d 100644 --- a/backend/lib/azimutt_web/templates/website/index.html.heex +++ b/backend/lib/azimutt_web/templates/website/index.html.heex @@ -12,11 +12,11 @@ Boost your database productivity. Right now!
- + - 710 stars + 777 stars - + Slack community @@ -162,10 +162,10 @@ Still not convinced? Take a look at use cases and features. We can answer any question, reach us on - Slack, - Twitter, - GitHub or - Email. + Slack, + Twitter, + GitHub or + Email.

Join 1500+ early adopters diff --git a/backend/lib/azimutt_web/views/layout_view.ex b/backend/lib/azimutt_web/views/layout_view.ex index 8d3ee085a..6652bfa31 100644 --- a/backend/lib/azimutt_web/views/layout_view.ex +++ b/backend/lib/azimutt_web/views/layout_view.ex @@ -47,8 +47,8 @@ defmodule AzimuttWeb.LayoutView do def twitter_site(%{assigns: %{seo: %{twitter_site: twitter_site}}}), do: twitter_site def twitter_site(_conn) do - if Azimutt.config(:twitter_url) do - "@" <> (Azimutt.config(:twitter_url) |> String.split("/") |> List.last()) + if Azimutt.config(:azimutt_twitter) do + "@" <> (Azimutt.config(:azimutt_twitter) |> String.split("/") |> List.last()) else "" end