Support for docker based self-hosting (#64)

* first commit with test and compile job

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding 'prepare' stage

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated ci script to include "test" compile phase

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding environment variables for connecting to postgresql

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated ci config for postgres

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* using non-alpine version of elixir

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* re-using the 'compile' artifacts and added explict env variables for testing

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* removing redundant deps fetching from common code

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* formatting using mix.format -- beware no-code changes!

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* added release config

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding consistent env variable for Database

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* more cleaning up of environment variables

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding releases config for enabling releases

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* cleaning up env configs

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Cleaned up config and prepared config for releases

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated CI script with new config for test

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added Dockerfile for creating production docker image

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding "docker" build job yay!

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* using non-slim version of debian and installing webpack

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding overlays for migrations on releases

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* restricting the docker built to master branch only

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* typo fix

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding "Hosting.md" to explain hosting instructions

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* removed the default comments

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added documentation related to env variables

* updated documentation and fixed typo

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated documentation

* Bumping up elixir version as `overlays` are only supported in latest version

read release notes: https://github.com/elixir-lang/elixir/releases/tag/v1.10.0

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding tarball assembly during release

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated HOSTING.md

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added support for db migration

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* minor corrections

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* initializing admin user

Admin user has been added in the "migration" phase. A default user is automatically created in the process. One can provide the related env variables, else a new one will be automatically created for you.

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Initial base domain update - phase#1

These changes are only meant for correct operating it under self-hosting. There are many other cosmetic changes, that require updates to email, site and other places where the original website and author is used.

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Using dedicated config variable `base_domain` instead

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding base_domain to releases config

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* removing the dedicated config "base_domain", relying on endpoint host

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Removed the usage of "Mix" in code!

It is bad practice to use "mix" module inside the code as in actual release this module is unavailable. Replacing this with a config environment variable

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added support for SMTP via Bamboo Smtp Adapter

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Capturing SMTP errors via Sentry

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Minor updates

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding junit formatter -- useful for generating test reports

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding documentation for default user

* Resolve "Gitlab Adoption: Add supported services in "Security & Compliance""

* bumping up the debian version to fix issues

fixing some vulnerabilities identified by the scanning tools

* More updates for self-hosting

Changes in most of the places to suit self-hosting. Although, there are some which have been left-off.

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* quick-dirty-fix!

* bumping up the db connect timeout

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* bumping up the db connect timeout

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* bumping up the db connect timeout

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* bumping up timeout - skipping MRs :-/

* removing restrictions on watching for changes

this stuff isn't working

* Update HOSTING.md

* renamed the module name

* reverting formatting-whitespace changes

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* reverting the name to release

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding docker-compose.yml and related instructions

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* using `plausible_url` instead of assuming `https`

this is because, it is much to test in local dev machines and in most cases there's already a layer above which is capable for `https` termination and http -> https upgrade

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* WIP: merging changes from upstream

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* wip: more changes

* Pushing in changes from upstream

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* changes to ci for testing

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* cleaning up and finishing clickhouse integration

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updating readme with hosting details
This commit is contained in:
Chandra Tungathurthi 2020-05-26 15:09:34 +02:00 committed by GitHub
parent 89e807b469
commit 5eb8929929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1246 additions and 243 deletions

125
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,125 @@
include:
- template: Container-Scanning.gitlab-ci.yml
- template: License-Scanning.gitlab-ci.yml
- template: SAST.gitlab-ci.yml
stages:
- prepare
- compile
- test
- build
- postbuild
.commons: &elixir-commons
image: elixir:1.10.3
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- $CI_PROJECT_DIR/.mix
- $CI_PROJECT_DIR/priv/plts
- ~/.hex/
before_script:
- mkdir -p $CI_PROJECT_DIR/priv/plts/
- mix local.hex --force && mix local.rebar --force
- chmod +x .gitlab/build-scripts/*
- source .gitlab/build-scripts/docker.gitlab.sh
deps:
<<: *elixir-commons
stage: prepare
variables:
MIX_HOME: $CI_PROJECT_DIR/.mix
script:
- mix deps.get
dependencies: []
artifacts:
paths:
- mix.lock
- deps
compile:
<<: *elixir-commons
stage: compile
script:
- mix compile
dependencies:
- deps
artifacts:
paths:
- mix.lock
- _build
- deps
license_scanning:
stage: compile
dependencies:
- deps
sast:
stage: compile
test:ex_unit:
<<: *elixir-commons
services:
- postgres
- name: yandex/clickhouse-server:20.3.9.70
alias: clickhouse
stage: test
variables:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
DATABASE_URL: postgres://postgres:postgres@postgres:5432/plausible_test?currentSchema=default
CLICKHOUSE_DATABASE_HOST: clickhouse
CLICKHOUSE_DATABASE_NAME: plausible_test
MIX_HOME: $CI_PROJECT_DIR/.mix
before_script:
- apt update && apt install -y clickhouse-client
- clickhouse-client --host clickhouse --query "CREATE DATABASE IF NOT EXISTS plausible_test"
script:
- mix test --cover
coverage: '/\[TOTAL\]\s+(\d+\.\d+)%/'
dependencies:
- compile
artifacts:
reports:
junit: plausible-report.xml
build:docker:
<<: *elixir-commons
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
stage: build
variables:
MIX_ENV: prod
MIX_HOME: $CI_PROJECT_DIR/.mix/
APP_VERSION: $CI_COMMIT_SHORT_SHA
before_script:
- chmod +x .gitlab/build-scripts/*
- source .gitlab/build-scripts/docker.gitlab.sh
- docker_create_config
script:
- docker_build_image
dependencies:
- compile
only:
- master
deploy:plausible:
stage: postbuild
script:
- "curl -X POST -F token=$PLAUSIBLE_DEPLOY_TOKEN -F ref=master -F variables[IMAGE_TAG]=${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA} $PLAUSIBLE_DEPLOY_PROJECT"
only:
- master
container_scanning:
stage: postbuild
image: registry.gitlab.com/gitlab-org/security-products/analyzers/klar:$CS_MAJOR_VERSION
variables:
CS_MAJOR_VERSION: 2
KLAR_TRACE: "true"
CLAIR_TRACE: "true"
CLAIR_OUTPUT: "medium"
CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE
CI_APPLICATION_TAG: ${CI_COMMIT_REF_SLUG}-$CI_COMMIT_SHORT_SHA

View File

@ -0,0 +1,17 @@
#!/bin/bash
set -e
chmod a+x /app/*.sh
if [[ "$1" = 'run' ]]; then
exec gosu plausibleuser /app/bin/plausible start
elif [[ "$1" = 'db' ]]; then
exec gosu plausibleuser /app/"$2".sh
else
exec "$@"
fi
exec "$@"

View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
############################
function docker_create_config() {
############################
mkdir -p /kaniko/.docker/
echo "###############"
echo "Logging into GitLab Container Registry with CI credentials for kaniko..."
echo "###############"
echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
echo ""
}
############################
function docker_build_image() {
############################
if [[ -f Dockerfile ]]; then
echo "###############"
echo "Building Dockerfile-based application..."
echo "###############"
/kaniko/executor \
--cache=true \
--context ${CI_PROJECT_DIR} \
--dockerfile ${CI_PROJECT_DIR}/Dockerfile \
--destination ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA} \
--destination ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-latest \
\
"$@"
else
echo "No Dockerfile found."
return 1
fi
}

82
Dockerfile Normal file
View File

@ -0,0 +1,82 @@
# we can not use the pre-built tar because the distribution is
# platform specific, it makes sense to build it in the docker
#### Builder
FROM elixir:1.10.3 as buildcontainer
# preparation
ARG APP_VER=0.0.1
ENV GOSU_VERSION 1.11
ENV MIX_ENV=prod
ENV NODE_ENV=production
ENV APP_VERSION=$APP_VER
RUN mkdir /app
WORKDIR /app
# install build dependencies
RUN apt-get update && \
apt-get install -y git build-essential nodejs yarn python npm --no-install-recommends && \
npm install npm@latest -g && \
npm install -g webpack
RUN apt-get install -y --no-install-recommends ca-certificates wget \
&& apt-get install -y --install-recommends gnupg2 dirmngr
# grab gosu for easy step-down from root
RUN set -x \
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
&& wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc" \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
&& gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
&& command -v gpgconf && gpgconf --kill all || : \
&& rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \
&& chmod +x /usr/local/bin/gosu \
&& gosu --version \
&& gosu nobody true
COPY config ./config
COPY assets ./assets
COPY priv ./priv
COPY lib ./lib
COPY mix.exs ./
COPY mix.lock ./
RUN mix local.hex --force && \
mix local.rebar --force && \
mix deps.get --only prod && \
mix deps.compile
RUN npm audit fix --prefix ./assets && \
npm install --prefix ./assets && \
npm run deploy --prefix ./assets && \
mix phx.digest priv/static
WORKDIR /app
COPY rel rel
RUN mix release plausible
# Main Docker Image
FROM debian:bullseye
LABEL maintainer="tckb <tckb@tgrthi.me>"
ENV LANG=C.UTF-8
RUN apt-get update && \
apt-get install -y bash openssl --no-install-recommends&& \
apt-get clean autoclean && \
apt-get autoremove --yes && \
rm -rf /var/lib/{apt,dpkg,cache,log}/
COPY .gitlab/build-scripts/docker-entrypoint.sh /entrypoint.sh
RUN chmod a+x /entrypoint.sh && \
useradd -d /app -u 1000 -s /bin/bash -m plausibleuser
COPY --from=buildcontainer /usr/local/bin/gosu /usr/local/bin/gosu
COPY --from=buildcontainer /app/_build/prod/rel/plausible /app
RUN chown -R plausibleuser:plausibleuser /app
WORKDIR /app
ENTRYPOINT ["/entrypoint.sh"]
CMD ["run"]

152
HOSTING.md Normal file
View File

@ -0,0 +1,152 @@
# Plausible Analytics
Self-hosting is possible based on the docker images and are automatically pushed into [Gitlab hosted docker](registry.gitlab.com/tckb-public/plausible) registry for all commits on `master` branch.
All `master-*` tags are considered to be stable and are persisted. Any other tag in the registry is considered to be for development purposes and/or unstable and are auto-deleted after a week.
### Building Docker image
Besides the GitlabCI, one can build docker image from [Dockerfile](./Dockerfile).
#### Up and Running
The repo supplies with a [Docker Compose](./docker-compose.yml) file, this serves as a sample for running Plausible with Docker.
In this sample, the db migration is done by default on startup, so you need to clean the data up every time you run:
First run
```bash
$ docker-compose up
```
subsequent runs--
```bash
$ docker-compose down
$ docker volume rm plausible_db-data -f
$ docker-compose up
```
### Non-docker building
It is possible to create a release artifact by running a release.
```elixir
MIX_ENV=prod mix release plausible
```
the release will create the pre-packed artifact at `_build/prod/rel/plausible/bin/plausible`, the release will also create a tarball at `_build/prod/` for convenience.
Note, that you have to feed in the related environment variables (see below `Environment Variables`)
## Database Migration
On the initial setup, a migration step is necessary to create database and table schemas needed for initial bootup.
Normally, this done by mix aliases like `ecto.setup` defined in the `mix.exs`. As this not available in "released" artifact, [`plausible_migration.ex`](./lib/plausible_migration.ex) facilitates this process.
The overlay [scripts](./rel/overlays) take care of these.
After the release, these are available under `_build/prod/rel/plausible` --
```bash
_build/prod/rel/plausible/createdb.sh
_build/prod/rel/plausible/migrate.sh
_build/prod/rel/plausible/rollback.sh
_build/prod/rel/plausible/seed.sh
```
the same is available in the docker images as follows --
```bash
docker run plausible:master-12add db createdb
docker run plausible:master-12add db migrate
docker run plausible:master-12add db rollback
docker run plausible:master-12add db seed
```
## Environment Variables
Plausible relies on the several services for operating, the expected environment variables are explaiend below.
### Server
Following are the variables that can be used to configure the availability of the server.
- HOST (*String*)
- The hosting address of the server. For running on local system, this can be set to **localhost**. In production systems, this can be your ingress host.
- PORT (*Number*)
- The port on which the server is available.
- SECRET_KEY_BASE (*String*)
- An internal secret key used by [Phoenix Framework](https://www.phoenixframework.org/). Follow the [instructions](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content) to generate one.
- ENVIRONMENT (*String*)
- The current running environment. _defaults to **prod**_
- APP_VERSION (*String*)
- The version of the app running. _defaults to current docker tag_
### Default User Generation
For self-hosting, a default user is generated during the [Database Migration](#Database Migration) to access Plausible. To be noted that, a default user is a user whose trial period expires in 100 Years ;).
It is *highly* recommended that you configure these parameters.
- ADMIN_USER_NAME
- The default ("admin") username. _if not provided, one will be generated for you_
- ADMIN_USER_EMAIL
- The default ("admin") user email. _if not provided, one will be generated for you_
- ADMIN_USER_PWD
- The default ("admin") user password. _if not provided, one will be generated for you_
### Mailer/SMTP Setup
- MAILER_ADAPTER (*String*)
- The adapter used for sending out e-mails. Available: `Bamboo.PostmarkAdapter` / `Bamboo.SMTPAdapter`
- MAILER_EMAIL (*String*)
- The email id to use for as _from_ address of all communications from Plausible.
In case of `Bamboo.SMTPAdapter` you need to supply the following variables:
- SMTP_HOST_ADDR (*String*)
- The host address of your smtp server.
- SMTP_HOST_PORT (*Number*)
- The port of your smtp server.
- SMTP_USER_NAME (*String*)
- The username/email for smtp auth.
- SMTP_USER_PWD (*String*)
- The password for smtp auth.
- SMTP_HOST_SSL_ENABLED (*Boolean String*)
- If ssl is enabled for connecting to Smtp, _defaults to `false`_
- SMTP_RETRIES (*Number*)
- Number of retries to make until mailer gives up. _defaults to `2`_
- SMTP_MX_LOOKUPS_ENABLED (*Boolean String*)
- If MX lookups should be done before sending out emails. _defaults to `false`_
### Database
Plausible uses postgresql as database for storing all the user-data. Use the following the variables to configure it.
- DATABASE_URL (*String*)
- The repo Url as dictated [here](https://hexdocs.pm/ecto/Ecto.Repo.html#module-urls)
- DATABASE_POOL_SIZE (*Number*)
- A default pool size for connecting to the database, defaults to *10*, a higher number is recommended for a production system.
- DATABASE_TLS_ENABLED (*Boolean String*)
- A flag that says whether to connect to the database via TLS, read [here](https://www.postgresql.org/docs/10/ssl-tcp.html)
For performance reasons, all the analytics events are stored in clickhouse:
- CLICKHOUSE_DATABASE_HOST (*String*)
- CLICKHOUSE_DATABASE_NAME (*String*)
- CLICKHOUSE_DATABASE_USER (*String*)
- CLICKHOUSE_DATABASE_PASSWORD (*String*)
- CLICKHOUSE_DATABASE_POOLSIZE (*Number*)
- A default pool size for connecting to the database, defaults to *10*, a higher number is recommended for a production system.
### External Services
- [Google Client](https://developers.google.com/api-client-library)
- GOOGLE_CLIENT_ID
- GOOGLE_CLIENT_SECRET
- [Sentry](https://sentry.io/)
- SENTRY_DSN
- [Paddle](https://paddle.com/)
- PADDLE_VENDOR_AUTH_CODE
- [PostMark](https://postmarkapp.com/), only in case of `Bamboo.PostmarkAdapter` mail adapter.
- POSTMARK_API_KEY
Apart from these, there are also the following integrations
- [Twitter](https://developer.twitter.com/en/docs)
- TWITTER_CONSUMER_KEY
- TWITTER_CONSUMER_SECRET
- TWITTER_ACCESS_TOKEN
- TWITTER_ACCESS_TOKEN_SECRET
- [Slack](https://api.slack.com/messaging/webhooks)
- SLACK_WEBHOOK

View File

@ -21,9 +21,7 @@ Interested? [Read more on our website](https://plausible.io)
### Can Plausible Analytics be self-hosted? ### Can Plausible Analytics be self-hosted?
At the moment we don't provide support for easily self-hosting the code. Currently, the purpose of keeping the code open-source is to be transparent with the community about how we collect and process data. The purpose of keeping the code open-source is to be transparent with the community about how we collect and process however, we do provide an experimental [docker-based self hosting](./HOSTING.md) setup. Please note that this is still in *alpha* stage and care should be taken while using it for production system.
Making Plausible Analytics easy to self-host, providing full documentation and support for the process is something we want to see happening in the future. There is [a GitHub thread](https://github.com/plausible-insights/plausible/issues/26) you can join and engage with to follow our progress in making Plausible Analytics easy to self-host.
### Why is Plausible Analytics not free like Google Analytics? ### Why is Plausible Analytics not free like Google Analytics?

View File

@ -1,19 +1,34 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
# General application configuration
use Mix.Config use Mix.Config
config :plausible, config :plausible,
ecto_repos: [Plausible.Repo] admin_user: System.get_env("ADMIN_USER_NAME", "admin"),
admin_email: System.get_env("ADMIN_USER_EMAIL", "admin@plausible.local"),
mailer_email: System.get_env("MAILER_EMAIL", "hello@plausible.local"),
admin_pwd: System.get_env("ADMIN_USER_PWD", "!@d3in"),
ecto_repos: [Plausible.Repo],
environment: System.get_env(Atom.to_string(Mix.env()), "dev")
config :plausible, :clickhouse,
hostname: System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost"),
database: System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_test"),
username: System.get_env("CLICKHOUSE_DATABASE_USER"),
password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"),
pool_size: 10
# Configures the endpoint # Configures the endpoint
config :plausible, PlausibleWeb.Endpoint, config :plausible, PlausibleWeb.Endpoint,
url: [host: "localhost"], url: [
secret_key_base: "/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg", host: System.get_env("HOST", "localhost"),
port: String.to_integer(System.get_env("PORT", "8000"))
],
http: [
port: String.to_integer(System.get_env("PORT", "8000"))
],
secret_key_base:
System.get_env(
"SECRET_KEY_BASE",
"/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg"
),
render_errors: [ render_errors: [
view: PlausibleWeb.ErrorView, view: PlausibleWeb.ErrorView,
accepts: ~w(html json) accepts: ~w(html json)
@ -21,11 +36,13 @@ config :plausible, PlausibleWeb.Endpoint,
pubsub: [name: Plausible.PubSub, adapter: Phoenix.PubSub.PG2] pubsub: [name: Plausible.PubSub, adapter: Phoenix.PubSub.PG2]
config :sentry, config :sentry,
dsn: "https://0350a42aa6234a2eaf1230866788598e@sentry.io/1382353", dsn: System.get_env("SENTRY_DSN"),
included_environments: [:prod, :staging], included_environments: [:prod, :staging],
environment_name: String.to_atom(Map.get(System.get_env(), "APP_ENV", "dev")), environment_name: String.to_atom(Map.get(System.get_env(), "MIX_ENV", "dev")),
enable_source_code_context: true, enable_source_code_context: true,
root_source_code_path: File.cwd! root_source_code_path: File.cwd!(),
tags: %{app_version: System.get_env("APP_VERSION", "0.0.1")},
context_lines: 5
# Configures Elixir's Logger # Configures Elixir's Logger
config :logger, :console, config :logger, :console,
@ -46,6 +63,7 @@ config :plausible,
google_api: Plausible.Google.Api google_api: Plausible.Google.Api
config :plausible, config :plausible,
# 30 minutes
session_timeout: 1000 * 60 * 30, # 30 minutes session_timeout: 1000 * 60 * 30, # 30 minutes
session_length_minutes: 30 session_length_minutes: 30
@ -53,6 +71,58 @@ config :plausible, :paddle,
vendor_id: "49430", vendor_id: "49430",
vendor_auth_code: System.get_env("PADDLE_VENDOR_AUTH_CODE") vendor_auth_code: System.get_env("PADDLE_VENDOR_AUTH_CODE")
config :plausible,
Plausible.Repo,
pool_size: String.to_integer(System.get_env("DATABASE_POOL_SIZE", "10")),
timeout: 300_000,
connect_timeout: 300_000,
handshake_timeout: 300_000,
url:
System.get_env(
"DATABASE_URL",
"postgres://postgres:postgres@127.0.0.1:5432/plausible_test?currentSchema=default"
),
ssl: false
config :plausible, :google,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
config :plausible, :slack, webhook: System.get_env("SLACK_WEBHOOK")
mailer_adapter = System.get_env("MAILER_ADAPTER", "Bamboo.PostmarkAdapter")
case mailer_adapter do
"Bamboo.PostmarkAdapter" ->
config :plausible, Plausible.Mailer,
adapter: :"Elixir.#{mailer_adapter}",
api_key: System.get_env("POSTMARK_API_KEY")
"Bamboo.SMTPAdapter" ->
config :plausible, Plausible.Mailer,
adapter: :"Elixir.#{mailer_adapter}",
server: System.fetch_env!("SMTP_HOST_ADDR"),
hostname: System.get_env("HOST", "localhost"),
port: System.fetch_env!("SMTP_HOST_PORT"),
username: System.fetch_env!("SMTP_USER_NAME"),
password: System.fetch_env!("SMTP_USER_PWD"),
tls: :if_available,
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
ssl: System.get_env("SMTP_HOST_SSL_ENABLED") || true,
retries: System.get_env("SMTP_RETRIES") || 2,
no_mx_lookups: System.get_env("SMTP_MX_LOOKUPS_ENABLED") || true,
auth: :always
_ ->
raise "Unknown mailer_adapter; expected SMTPAdapter or PostmarkAdapter"
end
config :plausible, :twitter,
consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"),
token: System.get_env("TWITTER_ACCESS_TOKEN"),
token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET")
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View File

@ -1,13 +1,7 @@
use Mix.Config use Mix.Config
# For development, we disable any cache and enable
# debugging and code reloading.
#
# The watchers configuration can be used to run external
# watchers to your application. For example, we use it
# with webpack to recompile .js and .css sources.
config :plausible, PlausibleWeb.Endpoint, config :plausible, PlausibleWeb.Endpoint,
http: [port: 8000], server: true,
debug_errors: true, debug_errors: true,
code_reloader: true, code_reloader: true,
check_origin: false, check_origin: false,
@ -35,21 +29,8 @@ config :logger, :console, format: "[$level] $message\n"
config :phoenix, :stacktrace_depth, 20 config :phoenix, :stacktrace_depth, 20
config :phoenix, :plug_init_mode, :runtime config :phoenix, :plug_init_mode, :runtime
config :plausible, :clickhouse,
hostname: "localhost",
database: "plausible_dev",
pool_size: 10
config :plausible, Plausible.Repo,
username: "postgres",
password: "postgres",
database: "plausible_dev",
hostname: "localhost",
pool_size: 10
config :plausible, Plausible.Mailer,
adapter: Bamboo.LocalAdapter
if File.exists?("config/dev.secret.exs") do if File.exists?("config/dev.secret.exs") do
import_config "dev.secret.exs" import_config "dev.secret.exs"
end end
config :logger, level: :debug

View File

@ -1,105 +1,7 @@
use Mix.Config use Mix.Config
# For production, don't forget to configure the url host # For the actual-production deployments we will use releases,
# to something meaningful, Phoenix uses this information # i.e., "releases.exs" is the _actual_ production config
# when generating URLs. # see "releases.exs"
#
# Note we also include the path to a cache manifest
# containing the digested version of static files. This
# manifest is generated by the `mix phx.digest` task,
# which you should run after static files are built and
# before starting your production server.
config :plausible, PlausibleWeb.Endpoint,
http: [:inet6, port: System.get_env("PORT") || 4000],
url: [host: System.get_env("HOST"), scheme: "https", port: 443],
cache_static_manifest: "priv/static/cache_manifest.json"
# Do not print debug messages in production import_config "releases.exs"
config :logger, level: :info
# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
# config :plausible, PlausibleWeb.Endpoint,
# ...
# url: [host: "example.com", port: 443],
# https: [
# :inet6,
# port: 443,
# cipher_suite: :strong,
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
# ]
#
# The `cipher_suite` is set to `:strong` to support only the
# latest and more secure SSL ciphers. This means old browsers
# and clients may not be supported. You can set it to
# `:compatible` for wider support.
#
# `:keyfile` and `:certfile` expect an absolute path to the key
# and cert in disk or a relative path inside priv, for example
# "priv/ssl/server.key". For all supported SSL configuration
# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
#
# We also recommend setting `force_ssl` in your endpoint, ensuring
# no data is ever sent via http, always redirecting to https:
#
# config :plausible, PlausibleWeb.Endpoint,
# force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.
# ## Using releases (distillery)
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start the server for all endpoints:
#
# config :phoenix, :serve_endpoints, true
#
# Alternatively, you can configure exactly which server to
# start per endpoint:
#
# config :plausible, PlausibleWeb.Endpoint, server: true
#
# Note you can't rely on `System.get_env/1` when using releases.
# See the releases documentation accordingly.
# Finally import the config/prod.secret.exs which should be versioned
# separately.
config :plausible, PlausibleWeb.Endpoint,
secret_key_base: System.get_env("SECRET_KEY_BASE")
config :plausible, :clickhouse,
hostname: System.get_env("CLICKHOUSE_DATABASE_HOST"),
database: System.get_env("CLICKHOUSE_DATABASE_NAME"),
username: System.get_env("CLICKHOUSE_DATABASE_USER"),
password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"),
pool_size: 30
# Configure your database
config :plausible, Plausible.Repo,
adapter: Ecto.Adapters.Postgres,
url: System.get_env("AVIEN_DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
timeout: 10_000,
ssl: true
config :plausible, :google,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
config :plausible, :slack,
webhook: System.get_env("SLACK_WEBHOOK")
config :plausible, Plausible.Mailer,
adapter: Bamboo.PostmarkAdapter,
api_key: System.get_env("POSTMARK_API_KEY")
config :plausible, :twitter, [
consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"),
token: System.get_env("TWITTER_ACCESS_TOKEN"),
token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET")
]

128
config/releases.exs Normal file
View File

@ -0,0 +1,128 @@
import Config
### Mandatory params Start
# it is highly recommended to change this parameters in production systems
# params are made optional to facilitate smooth release
port = System.get_env("PORT") || 8000
host = System.get_env("HOST", "localhost")
secret_key_base =
System.get_env(
"SECRET_KEY_BASE",
"/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg"
)
db_pool_size = String.to_integer(System.get_env("DATABASE_POOL_SIZE", "10"))
db_url =
System.get_env(
"DATABASE_URL",
"postgres://postgres:postgres@127.0.0.1:5432/plausible_test?currentSchema=default"
)
db_tls_enabled? = String.to_existing_atom(System.get_env("DATABASE_TLS_ENABLED", "false"))
admin_user = System.get_env("ADMIN_USER_NAME")
admin_email = System.get_env("ADMIN_USER_EMAIL")
admin_pwd = System.get_env("ADMIN_USER_PWD")
env = System.get_env("ENVIRONMENT", "prod")
mailer_adapter = System.get_env("MAILER_ADAPTER", "Bamboo.PostmarkAdapter")
mailer_email = System.get_env("MAILER_EMAIL", "hello@plausible.local")
app_version = System.get_env("APP_VERSION", "0.0.1")
ck_host = System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost")
ck_db = System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_dev")
ck_db_user = System.get_env("CLICKHOUSE_DATABASE_USER")
ck_db_pwd = System.get_env("CLICKHOUSE_DATABASE_PASSWORD")
ck_db_pool = System.get_env("CLICKHOUSE_DATABASE_POOLSIZE") || 10
### Mandatory params End
sentry_dsn = System.get_env("SENTRY_DSN")
paddle_auth_code = System.get_env("PADDLE_VENDOR_AUTH_CODE")
google_cid = System.get_env("GOOGLE_CLIENT_ID")
google_secret = System.get_env("GOOGLE_CLIENT_SECRET")
slack_hook_url = System.get_env("SLACK_WEBHOOK")
twitter_consumer_key = System.get_env("TWITTER_CONSUMER_KEY")
twitter_consumer_secret = System.get_env("TWITTER_CONSUMER_SECRET")
twitter_token = System.get_env("TWITTER_ACCESS_TOKEN")
twitter_token_secret = System.get_env("TWITTER_ACCESS_TOKEN_SECRET")
postmark_api_key = System.get_env("POSTMARK_API_KEY")
config :plausible,
admin_user: admin_user,
admin_email: admin_email,
admin_pwd: admin_pwd,
environment: env,
mailer_email: mailer_email
config :plausible, PlausibleWeb.Endpoint,
url: [host: host, port: port],
http: [
port: port
],
secret_key_base: secret_key_base,
cache_static_manifest: "priv/static/cache_manifest.json",
check_origin: false,
load_from_system_env: true,
server: true,
code_reloader: false
config :plausible,
Plausible.Repo,
pool_size: db_pool_size,
url: db_url,
adapter: Ecto.Adapters.Postgres,
ssl: db_tls_enabled?
config :sentry,
dsn: sentry_dsn,
environment_name: env,
release: app_version,
tags: %{app_version: app_version}
config :plausible, :paddle, vendor_auth_code: paddle_auth_code
config :plausible, :google,
client_id: google_cid,
client_secret: google_secret
config :plausible, :slack, webhook: slack_hook_url
config :plausible, :clickhouse,
hostname: ck_host,
database: ck_db,
username: ck_db_user,
password: ck_db_pwd,
pool_size: ck_db_pool
case mailer_adapter do
"Bamboo.PostmarkAdapter" ->
config :plausible, Plausible.Mailer,
adapter: :"Elixir.#{mailer_adapter}",
api_key: System.get_env("POSTMARK_API_KEY")
"Bamboo.SMTPAdapter" ->
config :plausible, Plausible.Mailer,
adapter: :"Elixir.#{mailer_adapter}",
server: System.fetch_env!("SMTP_HOST_ADDR"),
hostname: System.get_env("HOST", "localhost"),
port: System.fetch_env!("SMTP_HOST_PORT"),
username: System.fetch_env!("SMTP_USER_NAME"),
password: System.fetch_env!("SMTP_USER_PWD"),
tls: :if_available,
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
ssl: System.get_env("SMTP_HOST_SSL_ENABLED") || true,
retries: System.get_env("SMTP_RETRIES") || 2,
no_mx_lookups: System.get_env("SMTP_MX_LOOKUPS_ENABLED") || true,
auth: :always
_ ->
raise "Unknown mailer_adapter; expected SMTPAdapter or PostmarkAdapter"
end
config :plausible, :twitter,
consumer_key: twitter_consumer_key,
consumer_secret: twitter_consumer_secret,
token: twitter_token,
token_secret: twitter_token_secret
config :logger, level: :warn

View File

@ -13,24 +13,23 @@ config :logger, level: :warn
config :bcrypt_elixir, :log_rounds, 4 config :bcrypt_elixir, :log_rounds, 4
# Configure your database # Configure your database
config :plausible, Plausible.Repo, config :plausible,
username: "postgres", Plausible.Repo,
password: "postgres", pool: Ecto.Adapters.SQL.Sandbox
database: "plausible_test",
hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox
config :plausible, :clickhouse, config :plausible, Plausible.Mailer, adapter: Bamboo.TestAdapter
hostname: "localhost",
database: "plausible_test",
pool_size: 10
config :plausible, Plausible.Mailer,
adapter: Bamboo.TestAdapter
config :plausible, config :plausible,
paddle_api: Plausible.PaddleApi.Mock, paddle_api: Plausible.PaddleApi.Mock,
google_api: Plausible.Google.Api.Mock google_api: Plausible.Google.Api.Mock
config :junit_formatter,
report_file: "report.xml",
report_dir: File.cwd!(),
print_report_file: true,
prepend_project_name?: true,
include_filename?: true
config :plausible, config :plausible,
session_timeout: 0 session_timeout: 0

79
docker-compose.yml Normal file
View File

@ -0,0 +1,79 @@
# NOTE:
# This Docker-compose file is created as a sample and should not be used directly for production
# You can adjust the settings as-per your environment
version: "3.3"
services:
# As it says, this service is a fake smtp server which accepts SMTP connections
# the inbox is available at localhost:8025, in actuality, you need to use a proper smtp server
fakesmtp_server:
container_name: fakesmtp_server
image: mailhog/mailhog:v1.0.0
ports:
- 1025:1025
- 8025:8025
healthcheck:
test: echo | telnet 127.0.0.1 1025
plausible_db:
container_name: plausible_db
image: postgres:9.4
command: ["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"]
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=plausible_db
- POSTGRES_USER=postgres
ports:
- 5432:5432
plausible_events_db:
container_name: plausible_events_db
image: yandex/clickhouse-server
ports:
- 8123:8123
plausible:
container_name: plausible
build:
context: .
dockerfile: ./Dockerfile
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate &&/entrypoint.sh run"
environment:
- ENVIRONMENT=production
- PORT=8080
- SECRET_KEY_BASE=iYb1mP5cnmY+gUxo7C/h6XMigossPhzwd8/ic6LFnQ9Y58Fl1xduSWaPq0fHDdbn
- SIGNING_SALT=PL/THF0VMOzuv1bOcldjDzYFBLryvXNs
- HOST=localhost
- DATABASE_URL=postgres://postgres:postgres@plausible_db:5432/plausible
- DATABASE_TLS_ENABLED=false
- ADMIN_USER_NAME=admin
- ADMIN_USER_EMAIL=admin@plausible.local
- ADMIN_USER_PWD=admin@1234!
- APP_VERSION=test
- MAILER_ADAPTER=Bamboo.SMTPAdapter
- SMTP_HOST_ADDR=fakesmtp_server
- SMTP_HOST_PORT=1025
- SMTP_USER_NAME=fakeuser@plausible.local
- SMTP_USER_PWD=password
- SMTP_HOST_SSL_ENABLED=false
- SMTP_MX_LOOKUPS_ENABLED=false
- CLICKHOUSE_DATABASE_HOST=plausible_events_db
- CLICKHOUSE_DATABASE_NAME=plausible_events_db
depends_on:
- plausible_events_db
- plausible_db
- fakesmtp_server
ports:
- 80:8080
links:
- plausible_db
- plausible_events_db
- fakesmtp_server
volumes:
db-data:
driver: local

View File

@ -20,6 +20,7 @@ defmodule Mix.Tasks.HydrateClickhouse do
hydrate_events(repo) hydrate_events(repo)
end end
def create_events() do def create_events() do
ddl = """ ddl = """
CREATE TABLE IF NOT EXISTS events ( CREATE TABLE IF NOT EXISTS events (

View File

@ -39,7 +39,7 @@ defmodule Mix.Tasks.SendCheckStatsEmails do
defp send_check_stats_email(_, user) do defp send_check_stats_email(_, user) do
PlausibleWeb.Email.check_stats_email(user) PlausibleWeb.Email.check_stats_email(user)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
Repo.insert_all("check_stats_emails", [%{ Repo.insert_all("check_stats_emails", [%{
user_id: user.id, user_id: user.id,

View File

@ -91,7 +91,8 @@ defmodule Mix.Tasks.SendEmailReports do
pages: pages, pages: pages,
query: query, query: query,
name: name name: name
) |> Plausible.Mailer.deliver_now() )
|> Plausible.Mailer.send_email()
end end
defp weekly_report_sent(site, time) do defp weekly_report_sent(site, time) do

View File

@ -80,7 +80,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do
defp send_create_site_email(_, user) do defp send_create_site_email(_, user) do
PlausibleWeb.Email.create_site_email(user) PlausibleWeb.Email.create_site_email(user)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
Repo.insert_all("create_site_emails", [%{ Repo.insert_all("create_site_emails", [%{
user_id: user.id, user_id: user.id,
@ -94,7 +94,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do
defp send_setup_success_email(_, user, site) do defp send_setup_success_email(_, user, site) do
PlausibleWeb.Email.site_setup_success(user, site) PlausibleWeb.Email.site_setup_success(user, site)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
Repo.insert_all("setup_success_emails", [%{ Repo.insert_all("setup_success_emails", [%{
site_id: site.id, site_id: site.id,
@ -108,7 +108,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do
defp send_setup_help_email(_, user, site) do defp send_setup_help_email(_, user, site) do
PlausibleWeb.Email.site_setup_help(user, site) PlausibleWeb.Email.site_setup_help(user, site)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
Repo.insert_all("setup_help_emails", [%{ Repo.insert_all("setup_help_emails", [%{
site_id: site.id, site_id: site.id,

View File

@ -52,7 +52,7 @@ defmodule Mix.Tasks.SendTrialNotifications do
defp send_one_week_reminder(_, user) do defp send_one_week_reminder(_, user) do
PlausibleWeb.Email.trial_one_week_reminder(user) PlausibleWeb.Email.trial_one_week_reminder(user)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
end end
defp send_tomorrow_reminder(["--dry-run"], user) do defp send_tomorrow_reminder(["--dry-run"], user) do
@ -63,7 +63,7 @@ defmodule Mix.Tasks.SendTrialNotifications do
usage = Plausible.Billing.usage(user) usage = Plausible.Billing.usage(user)
PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage) PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
end end
defp send_today_reminder(["--dry-run"], user) do defp send_today_reminder(["--dry-run"], user) do
@ -74,7 +74,7 @@ defmodule Mix.Tasks.SendTrialNotifications do
usage = Plausible.Billing.usage(user) usage = Plausible.Billing.usage(user)
PlausibleWeb.Email.trial_upgrade_email(user, "today", usage) PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
end end
defp send_over_reminder(["--dry-run"], user) do defp send_over_reminder(["--dry-run"], user) do

View File

@ -3,7 +3,7 @@ defmodule Plausible.Google.Api do
@verified_permission_levels ["siteOwner", "siteFullUser", "siteRestrictedUser"] @verified_permission_levels ["siteOwner", "siteFullUser", "siteRestrictedUser"]
def authorize_url(site_id) do def authorize_url(site_id) do
if Mix.env() == :test do if Application.get_env(:plausible, :environment) == "test" do
"" ""
else else
"https://accounts.google.com/o/oauth2/v2/auth?client_id=#{client_id()}&redirect_uri=#{redirect_uri()}&prompt=consent&response_type=code&access_type=offline&scope=#{@scope}&state=#{site_id}" "https://accounts.google.com/o/oauth2/v2/auth?client_id=#{client_id()}&redirect_uri=#{redirect_uri()}&prompt=consent&response_type=code&access_type=offline&scope=#{@scope}&state=#{site_id}"

View File

@ -1,3 +1,17 @@
defmodule Plausible.Mailer do defmodule Plausible.Mailer do
use Bamboo.Mailer, otp_app: :plausible use Bamboo.Mailer, otp_app: :plausible
def send_email(email) do
try do
Plausible.Mailer.deliver_now(email)
rescue
error ->
Sentry.capture_exception(error,
stacktrace: __STACKTRACE__,
extra: %{extra: "Error while sending email"}
)
raise error
end
end
end end

234
lib/plausible_release.ex Normal file
View File

@ -0,0 +1,234 @@
defmodule Plausible.Release do
use Plausible.Repo
@app :plausible
@start_apps [
:postgrex,
:ecto
]
alias Mix.Tasks.HydrateClickhouse, as: Clickhouse
def init_admin do
{admin_email, admin_user, admin_pwd} =
validate_admin(
{Application.get_env(:plausible, :admin_email),
Application.get_env(:plausible, :admin_user),
Application.get_env(:plausible, :admin_pwd)}
)
{:ok, admin} = Plausible.Auth.create_user(admin_user, admin_email)
# set the password
{:ok, admin} = Plausible.Auth.User.set_password(admin, admin_pwd) |> Repo.update()
# bump-up the trail period
admin
|> Ecto.Changeset.cast(%{trial_expiry_date: Timex.today() |> Timex.shift(years: 100)}, [
:trial_expiry_date
])
|> Repo.update()
IO.puts("Admin user created successful!")
end
def migrate do
prepare()
Enum.each(repos(), &run_migrations_for/1)
init_admin()
IO.puts("Migrations successful!")
end
def seed do
prepare()
# Run seed script
Enum.each(repos(), &run_seeds_for/1)
# Signal shutdown
IO.puts("Success!")
end
def createdb do
prepare()
do_create_db()
IO.puts("Creation of Db successful!")
end
def rollback do
prepare()
get_step =
IO.gets("Enter the number of steps: ")
|> String.trim()
|> Integer.parse()
case get_step do
{int, _trailing} ->
Enum.each(repos(), fn repo -> run_rollbacks_for(repo, int) end)
IO.puts("Rollback successful!")
:error ->
IO.puts("Invalid integer")
end
end
##############################
defp validate_admin({nil, nil, nil}) do
random_user = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
random_pwd = :crypto.strong_rand_bytes(20) |> Base.encode64() |> binary_part(0, 20)
random_email = "#{random_user}@#{System.get_env("HOST")}"
IO.puts("generated admin user/password: #{random_email} / #{random_pwd}")
{random_email, random_user, random_pwd}
end
defp validate_admin({admin_email, admin_user, admin_password}) do
{admin_email, admin_user, admin_password}
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp run_seeds_for(repo) do
# Run the seed script if it exists
seed_script = seeds_path(repo)
if File.exists?(seed_script) do
IO.puts("Running seed script..")
Code.eval_file(seed_script)
end
end
defp run_migrations_for(repo) do
app = Keyword.get(repo.config, :otp_app)
IO.puts("Running migrations for #{app}")
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
defp do_create_db do
for repo <- repos() do
:ok = ensure_repo_created(repo)
end
do_create_ch_db()
end
defp do_create_ch_db() do
db_to_create = Keyword.get(Application.get_env(:plausible, :clickhouse),:database)
IO.puts("create #{inspect(db_to_create)} clickhouse database/tables if it doesn't exist")
Clickhousex.query(:clickhouse, "CREATE DATABASE IF NOT EXISTS #{db_to_create}", [])
tb_events = """
CREATE TABLE IF NOT EXISTS #{db_to_create}.events (
timestamp DateTime,
name String,
domain String,
user_id UInt64,
session_id UInt64,
hostname String,
pathname String,
referrer String,
referrer_source String,
initial_referrer String,
initial_referrer_source String,
country_code LowCardinality(FixedString(2)),
screen_size LowCardinality(String),
operating_system LowCardinality(String),
browser LowCardinality(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (name, domain, user_id, timestamp)
SETTINGS index_granularity = 8192
"""
Clickhousex.query(:clickhouse, tb_events, [])
tb_sessions = """
CREATE TABLE IF NOT EXISTS #{db_to_create}.sessions (
session_id UInt64,
sign Int8,
domain String,
user_id UInt64,
hostname String,
timestamp DateTime,
start DateTime,
is_bounce UInt8,
entry_page String,
exit_page String,
pageviews Int32,
events Int32,
duration UInt32,
referrer String,
referrer_source String,
country_code LowCardinality(FixedString(2)),
screen_size LowCardinality(String),
operating_system LowCardinality(String),
browser LowCardinality(String)
) ENGINE = CollapsingMergeTree(sign)
PARTITION BY toYYYYMM(start)
ORDER BY (domain, user_id, session_id, start)
SETTINGS index_granularity = 8192
"""
Clickhousex.query(:clickhouse, tb_sessions, [])
end
defp ensure_repo_created(repo) do
IO.puts("create #{inspect(repo)} database if it doesn't exist")
case repo.__adapter__.storage_up(repo.config) do
:ok -> :ok
{:error, :already_up} -> :ok
{:error, term} -> {:error, term}
end
end
defp run_rollbacks_for(repo, step) do
app = Keyword.get(repo.config, :otp_app)
IO.puts("Running rollbacks for #{app} (STEP=#{step})")
{:ok, _, _} =
Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, all: false, step: step))
end
defp prepare do
IO.puts("Loading #{@app}..")
# Load the code for myapp, but don't start it
:ok = Application.load(@app)
prepare_clickhouse()
IO.puts("Starting dependencies..")
# Start apps necessary for executing migrations
Enum.each(@start_apps, &Application.ensure_all_started/1)
# Start the Repo(s) for myapp
IO.puts("Starting repos..")
Enum.each(repos(), & &1.start_link(pool_size: 2))
end
defp prepare_clickhouse do
Application.ensure_all_started(:db_connection)
Application.ensure_all_started(:hackney)
Clickhousex.start_link([
scheme: :http,
port: 8123,
name: :clickhouse,
database: "default",
hostname: Keyword.get(Application.get_env(:plausible,:clickhouse),:hostname)
])
end
defp seeds_path(repo), do: priv_path_for(repo, "seeds.exs")
defp priv_path_for(repo, filename) do
app = Keyword.get(repo.config, :otp_app)
IO.puts("App: #{app}")
repo_underscore = repo |> Module.split() |> List.last() |> Macro.underscore()
Path.join([priv_dir(app), repo_underscore, filename])
end
defp priv_dir(app), do: "#{:code.priv_dir(app)}"
end

View File

@ -21,7 +21,7 @@ defmodule PlausibleWeb.AuthController do
url = PlausibleWeb.Endpoint.clean_url() <> "/claim-activation?token=#{token}" url = PlausibleWeb.Endpoint.clean_url() <> "/claim-activation?token=#{token}"
Logger.info(url) Logger.info(url)
email_template = PlausibleWeb.Email.activation_email(user, url) email_template = PlausibleWeb.Email.activation_email(user, url)
Plausible.Mailer.deliver_now(email_template) Plausible.Mailer.send_email(email_template)
conn |> render("register_success.html", email: user.email, layout: {PlausibleWeb.LayoutView, "focus.html"}) conn |> render("register_success.html", email: user.email, layout: {PlausibleWeb.LayoutView, "focus.html"})
{:error, changeset} -> {:error, changeset} ->
render(conn, "register_form.html", changeset: changeset, layout: {PlausibleWeb.LayoutView, "focus.html"}) render(conn, "register_form.html", changeset: changeset, layout: {PlausibleWeb.LayoutView, "focus.html"})
@ -34,7 +34,7 @@ defmodule PlausibleWeb.AuthController do
case Auth.create_user(name, email) do case Auth.create_user(name, email) do
{:ok, user} -> {:ok, user} ->
PlausibleWeb.Email.welcome_email(user) PlausibleWeb.Email.welcome_email(user)
|> Plausible.Mailer.deliver_now() |> Plausible.Mailer.send_email()
conn conn
|> put_session(:current_user_id, user.id) |> put_session(:current_user_id, user.id)

View File

@ -6,6 +6,10 @@ defmodule PlausibleWeb.BillingController do
plug PlausibleWeb.RequireAccountPlug plug PlausibleWeb.RequireAccountPlug
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def change_plan_form(conn, _params) do def change_plan_form(conn, _params) do
subscription = Billing.active_subscription_for(conn.assigns[:current_user].id) subscription = Billing.active_subscription_for(conn.assigns[:current_user].id)

View File

@ -70,7 +70,7 @@ defmodule PlausibleWeb.PageController do
end end
def submit_contact_form(conn, %{"text" => text, "email" => email}) do def submit_contact_form(conn, %{"text" => text, "email" => email}) do
PlausibleWeb.Email.feedback(email, text) |> Plausible.Mailer.deliver_now PlausibleWeb.Email.feedback(email, text) |> Plausible.Mailer.send_email()
render(conn, "contact_thanks.html") render(conn, "contact_thanks.html")
end end

View File

@ -6,6 +6,10 @@ defmodule PlausibleWeb.StatsController do
plug PlausibleWeb.AuthorizeStatsPlug when action in [:stats, :csv_export] plug PlausibleWeb.AuthorizeStatsPlug when action in [:stats, :csv_export]
def base_domain() do
PlausibleWeb.Endpoint.host()
end
def stats(conn, _params) do def stats(conn, _params) do
site = conn.assigns[:site] site = conn.assigns[:site]
user = conn.assigns[:current_user] user = conn.assigns[:current_user]
@ -14,7 +18,7 @@ defmodule PlausibleWeb.StatsController do
redirect(conn, to: "/billing/upgrade") redirect(conn, to: "/billing/upgrade")
else else
if Stats.has_pageviews?(site) do if Stats.has_pageviews?(site) do
demo = site.domain == "plausible.io" demo = site.domain == base_domain()
offer_email_report = get_session(conn, site.domain <> "_offer_email_report") offer_email_report = get_session(conn, site.domain <> "_offer_email_report")
conn conn
@ -108,4 +112,3 @@ defmodule PlausibleWeb.StatsController do
end end
end end
end end

View File

@ -2,10 +2,18 @@ defmodule PlausibleWeb.Email do
use Bamboo.Phoenix, view: PlausibleWeb.EmailView use Bamboo.Phoenix, view: PlausibleWeb.EmailView
import Bamboo.PostmarkHelper import Bamboo.PostmarkHelper
def mailer_email_from do
Application.get_env(:plausible, :mailer_email)
end
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def activation_email(user, link) do def activation_email(user, link) do
base_email() base_email()
|> to(user.email) |> to(user.email)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("activation-email") |> tag("activation-email")
|> subject("Activate your Plausible free trial") |> subject("Activate your Plausible free trial")
|> render("activation_email.html", name: user.name, link: link) |> render("activation_email.html", name: user.name, link: link)
@ -14,7 +22,7 @@ defmodule PlausibleWeb.Email do
def welcome_email(user) do def welcome_email(user) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("welcome-email") |> tag("welcome-email")
|> subject("Welcome to Plausible") |> subject("Welcome to Plausible")
|> render("welcome_email.html", user: user) |> render("welcome_email.html", user: user)
@ -23,7 +31,7 @@ defmodule PlausibleWeb.Email do
def create_site_email(user) do def create_site_email(user) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("create-site-email") |> tag("create-site-email")
|> subject("Your Plausible setup: Add your website details") |> subject("Your Plausible setup: Add your website details")
|> render("create_site_email.html", user: user) |> render("create_site_email.html", user: user)
@ -32,7 +40,7 @@ defmodule PlausibleWeb.Email do
def site_setup_help(user, site) do def site_setup_help(user, site) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("help-email") |> tag("help-email")
|> subject("Your Plausible setup: Waiting for the first page views") |> subject("Your Plausible setup: Waiting for the first page views")
|> render("site_setup_help_email.html", user: user, site: site) |> render("site_setup_help_email.html", user: user, site: site)
@ -41,7 +49,7 @@ defmodule PlausibleWeb.Email do
def site_setup_success(user, site) do def site_setup_success(user, site) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("setup-success-email") |> tag("setup-success-email")
|> subject("Plausible is now tracking your website stats") |> subject("Plausible is now tracking your website stats")
|> render("site_setup_success_email.html", user: user, site: site) |> render("site_setup_success_email.html", user: user, site: site)
@ -50,7 +58,7 @@ defmodule PlausibleWeb.Email do
def check_stats_email(user) do def check_stats_email(user) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("check-stats-email") |> tag("check-stats-email")
|> subject("Check your Plausible website stats") |> subject("Check your Plausible website stats")
|> render("check_stats_email.html", user: user) |> render("check_stats_email.html", user: user)
@ -59,7 +67,7 @@ defmodule PlausibleWeb.Email do
def password_reset_email(email, reset_link) do def password_reset_email(email, reset_link) do
base_email() base_email()
|> to(email) |> to(email)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("password-reset-email") |> tag("password-reset-email")
|> subject("Plausible password reset") |> subject("Plausible password reset")
|> render("password_reset_email.html", reset_link: reset_link) |> render("password_reset_email.html", reset_link: reset_link)
@ -68,7 +76,7 @@ defmodule PlausibleWeb.Email do
def trial_one_week_reminder(user) do def trial_one_week_reminder(user) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("trial-one-week-reminder") |> tag("trial-one-week-reminder")
|> subject("Your Plausible trial expires next week") |> subject("Your Plausible trial expires next week")
|> render("trial_one_week_reminder.html", user: user) |> render("trial_one_week_reminder.html", user: user)
@ -77,7 +85,7 @@ defmodule PlausibleWeb.Email do
def trial_upgrade_email(user, day, pageviews) do def trial_upgrade_email(user, day, pageviews) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("trial-upgrade-email") |> tag("trial-upgrade-email")
|> subject("Your Plausible trial ends #{day}") |> subject("Your Plausible trial ends #{day}")
|> render("trial_upgrade_email.html", user: user, day: day, pageviews: pageviews) |> render("trial_upgrade_email.html", user: user, day: day, pageviews: pageviews)
@ -86,7 +94,7 @@ defmodule PlausibleWeb.Email do
def trial_over_email(user) do def trial_over_email(user) do
base_email() base_email()
|> to(user) |> to(user)
|> from("Uku Taht <uku@plausible.io>") |> from(mailer_email_from())
|> tag("trial-over-email") |> tag("trial-over-email")
|> subject("Your Plausible trial has ended") |> subject("Your Plausible trial has ended")
|> render("trial_over_email.html", user: user) |> render("trial_over_email.html", user: user)

View File

@ -44,11 +44,11 @@ defmodule PlausibleWeb.Endpoint do
def clean_url() do def clean_url() do
url = PlausibleWeb.Endpoint.url url = PlausibleWeb.Endpoint.url
case Application.get_env(:plausible, :environment) do
if Mix.env() == :prod do # do not truncate the port in case of dev or test environment
URI.parse(url) |> Map.put(:port, nil) |> URI.to_string() env when env in ["dev", "test"] -> url
else # in most deployments, there's a layer above the above
url _ -> URI.parse(url) |> Map.put(:port, nil) |> URI.to_string()
end end
end end
end end

View File

@ -29,7 +29,7 @@ defmodule PlausibleWeb.Router do
plug :fetch_session plug :fetch_session
end end
if Mix.env == :dev do if Application.get_env(:plausible, :environment) == "dev" do
forward "/sent-emails", Bamboo.SentEmailViewerPlug forward "/sent-emails", Bamboo.SentEmailViewerPlug
end end

View File

@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>,
<br /><br /> <br /><br />
Plausible is tracking your website stats without compromising the user experience and the privacy of your visitors. Plausible is tracking your website stats without compromising the user experience and the privacy of your visitors.
<br /><br /> <br /><br />
<%= link("View your Plausible dashboard now", to: "https://plausible.io/") %> for the most valuable traffic insights at a glance. <%= link("View your Plausible dashboard now", to: "#{plausible_url()}") %> for the most valuable traffic insights at a glance.
<br /><br /> <br /><br />
Do reply back to this email if you have any questions or need some guidance. Do reply back to this email if you have any questions or need some guidance.
<br /></br> <br /></br>
@ -11,4 +11,4 @@ Uku Taht
<br /><br /> <br /><br />
-- --
<br /><br /> <br /><br />
https://plausible.io <%= plausible_url() %>

View File

@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>,
<br /><br /> <br /><br />
You've activated your free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool. You've activated your free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.
<br /><br /> <br /><br />
<%= link("Click here", to: "https://plausible.io/sites/new") %> to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics. <%= link("Click here", to: "#{plausible_url()}/sites/new") %> to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics.
<br /><br /> <br /><br />
Do reply back to this email if you have any questions or need some guidance. Do reply back to this email if you have any questions or need some guidance.
<br /></br> <br /></br>
@ -11,4 +11,4 @@ Uku Taht
<br /><br /> <br /><br />
-- --
<br /><br /> <br /><br />
https://plausible.io <%= plausible_url() %>

View File

@ -4,7 +4,7 @@ Hey <%= user_salutation(@user) %>,
You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool. You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.
<br /><br /> <br /><br />
<% end %> <% end %>
To finish your setup for <%= @site.domain %>, you need to install <%= link("this lightweight line of JavaScript code", to: "https://plausible.io/#{URI.encode_www_form(@site.domain)}/snippet") %> into your site to start collecting visitor statistics. To finish your setup for <%= @site.domain %>, you need to install <%= link("this lightweight line of JavaScript code", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/snippet") %> into your site to start collecting visitor statistics.
<br /><br /> <br /><br />
This Plausible script is 14 times smaller than Google Analytics script so youll have a fast loading site while getting all the important traffic insights on one single page. This Plausible script is 14 times smaller than Google Analytics script so youll have a fast loading site while getting all the important traffic insights on one single page.
<br /><br /> <br /><br />
@ -15,4 +15,4 @@ Uku Taht
<br /><br /> <br /><br />
-- --
<br /><br /> <br /><br />
https://plausible.io <%= plausible_url() %>

View File

@ -2,13 +2,13 @@ Hey <%= user_salutation(@user) %>,
<br /><br /> <br /><br />
Congrats! The Plausible script has been installed correctly on <%= link(@site.domain, to: "https://#{@site.domain}") %>. Your website traffic is now being tracked without compromising the user experience and the privacy of your visitors. Congrats! The Plausible script has been installed correctly on <%= link(@site.domain, to: "https://#{@site.domain}") %>. Your website traffic is now being tracked without compromising the user experience and the privacy of your visitors.
<br /><br /> <br /><br />
<%= link("Check your stats", to: "https://plausible.io/#{URI.encode_www_form(@site.domain)}") %> <%= link("Check your stats", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}") %>
<br /><br /> <br /><br />
<%= if Plausible.Billing.on_trial?(@user) do %> <%= if Plausible.Billing.on_trial?(@user) do %>
You're on a 30-day unlimited-use free trial with no obligations so do take your time to explore your simple and privacy-friendly website analytics dashboard. You're on a 30-day unlimited-use free trial with no obligations so do take your time to explore your simple and privacy-friendly website analytics dashboard.
<br /><br /> <br /><br />
<% end %> <% end %>
PS: Plausible is fully open-source and our public roadmap is defined by the community. <%= link("Leave your feedback", to: "https://plausible.io/feedback") %> and have your say on metrics and features we should be adding next. PS: Plausible is fully open-source and our public roadmap is defined by the community. <%= link("Leave your feedback", to: "#{plausible_url()}/feedback") %> and have your say on metrics and features we should be adding next.
<br /><br /> <br /><br />
Do reply back to this email if you have any questions. Do reply back to this email if you have any questions.
<br /></br> <br /></br>
@ -17,4 +17,5 @@ Uku Taht
<br /><br /> <br /><br />
-- --
<br /><br /> <br /><br />
https://plausible.io <%= plausible_url() %>

View File

@ -4,10 +4,10 @@ Time flies! Your 30-day free trial of Plausible will end next week.
<br /><br /> <br /><br />
Over the last three weeks, I hope you got to experience the potential benefits of having website stats in a simple dashboard while respecting the privacy of your visitors, not annoying them with the cookie and privacy notices and still having a fast loading site. Over the last three weeks, I hope you got to experience the potential benefits of having website stats in a simple dashboard while respecting the privacy of your visitors, not annoying them with the cookie and privacy notices and still having a fast loading site.
<br /><br /> <br /><br />
In order to continue receiving valuable website traffic insights at a glance, youll need to <%= link("Upgrade your account", to: "https://plausible.io/billing/upgrade") %>. In order to continue receiving valuable website traffic insights at a glance, youll need to <%= link("Upgrade your account", to: "#{plausible_url()}/billing/upgrade") %>.
<br /><br /> <br /><br />
If you have any questions or feedback for me, feel free to reply to this email. If you have any questions or feedback for me, feel free to reply to this email.
<br /><br /> <br /><br />
Thanks,<br /> Thanks,<br />
Uku Taht<br /> Uku Taht<br />
https://plausible.io <%= plausible_url() %>

View File

@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>,
<br /><br /> <br /><br />
Your free Plausible trial has now expired. Upgrade your account to continue receiving valuable website traffic insights at a glance while respecting the privacy of your visitors and still having a fast loading site. <br /><br /> Your free Plausible trial has now expired. Upgrade your account to continue receiving valuable website traffic insights at a glance while respecting the privacy of your visitors and still having a fast loading site. <br /><br />
<%= link("Upgrade now", to: "https://plausible.io/billing/upgrade") %> <%= link("Upgrade now", to: "#{plausible_url()}/billing/upgrade") %>
<br /><br /> <br /><br />
We will keep recording stats for another month to give you time to upgrade. We will keep recording stats for another month to give you time to upgrade.
@ -14,4 +14,4 @@ Founder, Plausible Insights
<br /><br /> <br /><br />
-- --
<br /><br /> <br /><br />
https://plausible.io <%= plausible_url() %>

View File

@ -6,11 +6,11 @@ In the last month, your account has used <%= PlausibleWeb.AuthView.delimit_integ
Based on that we recommend you select the <%= suggested_plan_name(@pageviews) %> plan which runs at <%= suggested_plan_cost(@pageviews) %>. Based on that we recommend you select the <%= suggested_plan_name(@pageviews) %> plan which runs at <%= suggested_plan_cost(@pageviews) %>.
You can also go with yearly billing to get 33% off on your plan. You can also go with yearly billing to get 33% off on your plan.
<br /><br /> <br /><br />
<%= link("Upgrade now", to: "https://plausible.io/billing/upgrade") %> <%= link("Upgrade now", to: "#{plausible_url()}/billing/upgrade") %>
<br /><br /> <br /><br />
Have a question, feedback or need some guidance? Just reply to this email to get in touch! Have a question, feedback or need some guidance? Just reply to this email to get in touch!
<br /><br /> <br /><br />
<br /><br /> <br /><br />
Thanks,<br /> Thanks,<br />
Uku Taht<br /> Uku Taht<br />
https://plausible.io <%= plausible_url() %>

View File

@ -3,7 +3,7 @@ Hey <%= user_salutation(@user) %>,
I'm building Plausible to provide a simple and ethical approach to tracking website visitors. I'm building Plausible to provide a simple and ethical approach to tracking website visitors.
I'm super excited to have you on board! I'm super excited to have you on board!
<br /><br /> <br /><br />
To start collecting stats, you need to <%= link("add a site on Plausible", to: "https://plausible.io/sites/new") %>. To start collecting stats, you need to <%= link("add a site on Plausible", to: "#{plausible_url()}/sites/new") %>.
<br /><br /> <br /><br />
Have a question, feedback or need some guidance? Do reply back to this email. Have a question, feedback or need some guidance? Do reply back to this email.
<br /><br /> <br /><br />
@ -12,4 +12,4 @@ Uku Taht
<br /><br /> <br /><br />
-- --
<br /><br /> <br /><br />
https://plausible.io <%= plausible_url() %>

View File

@ -6,8 +6,8 @@
</div> </div>
<ul class="text-center md:text-left my-4 md:m-0"> <ul class="text-center md:text-left my-4 md:m-0">
<li>Read our <a href="/blog/" class="light-text font-medium mr-4 underline">Blog</a></li> <li>Read our <a href="/blog/" class="light-text font-medium mr-4 underline">Blog</a></li>
<li>Study the <a href="https://docs.plausible.io" target="_blank" class="light-text font-medium mr-4 underline">Documentation</a></li> <li>Study the <%= link("Documentation", to: "https://docs.#{base_domain()}",target: "_blank",class: "light-text font-medium mr-4 underline") %></li>
<li>Check out the <a href="/plausible.io" class="light-text font-medium underline">Live Demo</a></li> <li>Check out the <%= link("Live Demo", to: "/plausible.io",class: "light-text font-medium underline") %></li>
</ul> </ul>
<ul class="text-center md:text-left my-4 md:m-0"> <ul class="text-center md:text-left my-4 md:m-0">
<li>Give us <a href="/feedback" target="_blank" class="light-text font-medium mr-4 underline">Feedback</a></li> <li>Give us <a href="/feedback" target="_blank" class="light-text font-medium mr-4 underline">Feedback</a></li>

View File

@ -1,4 +1,4 @@
<%= if !@conn.assigns[:skip_plausible_tracking] do %> <%= if !@conn.assigns[:skip_plausible_tracking] do %>
<script async defer src="https://plausible.io/js/plausible.js"></script> <script async defer src="<%="#{plausible_url()}/js/plausible.js"%>"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script> <script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
<% end %> <% end %>

View File

@ -1,6 +1,6 @@
<div class="max-w-md mx-auto py-12 just-text"> <div class="max-w-md mx-auto py-12 just-text">
<h1 class="text-xl font-black mb-4">Privacy Policy</h1> <h1 class="text-xl font-black mb-4">Privacy Policy</h1>
<p>Your privacy is important to us. It is Plausible Insights' policy to respect your privacy regarding any information we may collect from you across our website, <a href="http://plausible.io">http://plausible.io</a>, and other sites we own and operate.</p> <p>Your privacy is important to us. It is Plausible Analytics' policy to respect your privacy regarding any information we may collect from you across our website, <%= link(plausible_url(), to: plausible_url()) %>, and other sites we own and operate.</p>
<p>We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why were collecting it and how it will be used.</p> <p>We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why were collecting it and how it will be used.</p>
<p>We only retain collected information for as long as necessary to provide you with your requested service. What data we store, well protect within commercially acceptable means to prevent loss and theft, as well as unauthorised access, disclosure, copying, use or modification.</p> <p>We only retain collected information for as long as necessary to provide you with your requested service. What data we store, well protect within commercially acceptable means to prevent loss and theft, as well as unauthorised access, disclosure, copying, use or modification.</p>
<p>We dont share any personally identifying information publicly or with third-parties, except when required to by law.</p> <p>We dont share any personally identifying information publicly or with third-parties, except when required to by law.</p>

View File

@ -1,7 +1,7 @@
<div class="max-w-2xl mx-auto leading-normal py-12 just-text"> <div class="max-w-2xl mx-auto leading-normal py-12 just-text">
<h1 class="text-2xl font-black mb-4">Terms of Service</h1> <h1 class="text-2xl font-black mb-4">Terms of Service</h1>
<h3 class="text-lg font-black">1. Terms</h3> <h3 class="text-lg font-black">1. Terms</h3>
<p>By accessing the website at <a href="http://plausible.io">http://plausible.io</a>, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.</p> <p>By accessing the website at <%= link(plausible_url(), to: plausible_url()) %>, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.</p>
<h3 class="text-lg font-black">2. Use License</h3> <h3 class="text-lg font-black">2. Use License</h3>
<ol type="a" class="list-decimal pl-4"> <ol type="a" class="list-decimal pl-4">
<li>Permission is granted to temporarily download one copy of the materials (information or software) on Plausible Insights' website for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this license you may not: <li>Permission is granted to temporarily download one copy of the materials (information or software) on Plausible Insights' website for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this license you may not:

View File

@ -3,8 +3,8 @@
<ol class="list-disc pl-4 my-4"> <ol class="list-disc pl-4 my-4">
<li>Go to your DNS providers website</li> <li>Go to your DNS providers website</li>
<li class="mt-4">Create a new CNAME record for <code><%= @site.custom_domain.domain %></code></li> <li class="mt-4">Create a new CNAME record for <code><%= @site.custom_domain.domain %></code></li>
<li class="mt-4">Point the record to <code>custom.plausible.io.</code> (including the dot)</li> <li class="mt-4">Point the record to <code>custom.<%= plausible_url() %>.</code> (including the dot)</li>
</ol> </ol>
<%= link("Done ->", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/snippet", class: "button w-full mt-6") %> <%= link("Done ->", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/snippet", class: "button w-full mt-6") %>
</div> </div>

View File

@ -35,7 +35,7 @@
<%= if @site.public do %> <%= if @site.public do %>
Stats for <%= @site.domain %> are currently <b>public</b>. Anyone with the following link can view the stats: Stats for <%= @site.domain %> are currently <b>public</b>. Anyone with the following link can view the stats:
<div class="relative text-sm mt-4"> <div class="relative text-sm mt-4">
<input type="text" id="public-link" value="https://plausible.io/<%= URI.encode_www_form(@site.domain) %>" class="transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-16 text-gray-700 appearance-none focus:outline-none" /> <input type="text" id="public-link" value="<%= base_domain() <> "/" <> URI.encode_www_form(@site.domain)%>" class="transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-16 text-gray-700 appearance-none focus:outline-none" />
<a onclick="var input = document.getElementById('public-link'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="absolute right-0 text-indigo-700 font-bold p-2"> <a onclick="var input = document.getElementById('public-link'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="absolute right-0 text-indigo-700 font-bold p-2">
<svg class="feather-sm"><use xlink:href="#feather-copy" /></svg> <svg class="feather-sm"><use xlink:href="#feather-copy" /></svg>
</a> </a>
@ -112,7 +112,7 @@
</p> </p>
<% else %> <% else %>
<p class="text-gray-700 mt-6"> <p class="text-gray-700 mt-6">
Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.plausible.io/google-search-console-integration#1-add-your-site-on-the-search-console", class: "text-indigo-500") %> on Search Console first. Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.#{base_domain()}/google-search-console-integration#1-add-your-site-on-the-search-console", class: "text-indigo-500") %> on Search Console first.
</p> </p>
<% end %> <% end %>
@ -132,7 +132,7 @@
<%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-4") %> <%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-4") %>
<div class="text-gray-700 mt-8"> <div class="text-gray-700 mt-8">
NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo-500") %> NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.#{base_domain()}/google-search-console-integration", class: "text-indigo-500") %>
</div> </div>
<% end %> <% end %>
</div> </div>

View File

@ -11,6 +11,18 @@ defmodule PlausibleWeb.AuthView do
"free_10k" => "10k / free" "free_10k" => "10k / free"
} }
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def base_domain do
PlausibleWeb.Endpoint.host()
end
def plausible_url do
PlausibleWeb.Endpoint.clean_url()
end
def subscription_name(subscription) do def subscription_name(subscription) do
@subscription_names[subscription.paddle_plan_id] @subscription_names[subscription.paddle_plan_id]
end end

View File

@ -1,6 +1,18 @@
defmodule PlausibleWeb.BillingView do defmodule PlausibleWeb.BillingView do
use PlausibleWeb, :view use PlausibleWeb, :view
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def base_domain do
PlausibleWeb.Endpoint.host()
end
def plausible_url do
PlausibleWeb.Endpoint.clean_url()
end
def present_date(date) do def present_date(date) do
Date.from_iso8601!(date) Date.from_iso8601!(date)
|> Timex.format!("{D} {Mshort} {YYYY}") |> Timex.format!("{D} {Mshort} {YYYY}")

View File

@ -1,6 +1,18 @@
defmodule PlausibleWeb.EmailView do defmodule PlausibleWeb.EmailView do
use PlausibleWeb, :view use PlausibleWeb, :view
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def plausible_url do
PlausibleWeb.Endpoint.clean_url()
end
def base_domain() do
PlausibleWeb.Endpoint.host()
end
def user_salutation(user) do def user_salutation(user) do
if user.name do if user.name do
String.split(user.name) |> List.first String.split(user.name) |> List.first

View File

@ -1,6 +1,18 @@
defmodule PlausibleWeb.LayoutView do defmodule PlausibleWeb.LayoutView do
use PlausibleWeb, :view use PlausibleWeb, :view
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def base_domain do
PlausibleWeb.Endpoint.host()
end
def plausible_url do
PlausibleWeb.Endpoint.clean_url()
end
def home_dest(conn) do def home_dest(conn) do
if conn.assigns[:current_user] do if conn.assigns[:current_user] do
"/sites" "/sites"

View File

@ -1,3 +1,15 @@
defmodule PlausibleWeb.PageView do defmodule PlausibleWeb.PageView do
use PlausibleWeb, :view use PlausibleWeb, :view
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def base_domain do
PlausibleWeb.Endpoint.host()
end
def plausible_url do
PlausibleWeb.Endpoint.clean_url()
end
end end

View File

@ -1,7 +1,19 @@
defmodule PlausibleWeb.SiteView do defmodule PlausibleWeb.SiteView do
use PlausibleWeb, :view use PlausibleWeb, :view
def goal_name(%Plausible.Goal{page_path: page_path}) when is_binary(page_path) do def admin_email do
Application.get_env(:plausible, :admin_email)
end
def plausible_url do
PlausibleWeb.Endpoint.clean_url()
end
def base_domain() do
PlausibleWeb.Endpoint.host()
end
def goal_name(%Plausible.Goal{page_path: page_path}) when is_binary(page_path) do
"Visit " <> page_path "Visit " <> page_path
end end
@ -14,11 +26,12 @@ defmodule PlausibleWeb.SiteView do
end end
def snippet(site) do def snippet(site) do
tracker = if site.custom_domain do tracker =
"https://" <> site.custom_domain.domain <> "/js/index.js" if site.custom_domain do
else "https://" <> site.custom_domain.domain <> "/js/index.js"
"https://plausible.io/js/plausible.js" else
end "#{plausible_url()}/js/plausible.js"
end
""" """
<script async defer data-domain="#{site.domain}" src="#{tracker}"></script> <script async defer data-domain="#{site.domain}" src="#{tracker}"></script>

View File

@ -1,6 +1,18 @@
defmodule PlausibleWeb.StatsView do defmodule PlausibleWeb.StatsView do
use PlausibleWeb, :view use PlausibleWeb, :view
def admin_email do
Application.get_env(:plausible, :admin_email)
end
def base_domain do
PlausibleWeb.Endpoint.host()
end
def plausible_url do
PlausibleWeb.Endpoint.clean_url()
end
def large_number_format(n) do def large_number_format(n) do
cond do cond do
n >= 1_000 && n < 1_000_000 -> n >= 1_000 && n < 1_000_000 ->

37
mix.exs
View File

@ -4,13 +4,23 @@ defmodule Plausible.MixProject do
def project do def project do
[ [
app: :plausible, app: :plausible,
version: "0.1.0", version: System.get_env("APP_VERSION", "0.0.1"),
elixir: "~> 1.5", elixir: "~> 1.10",
elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
aliases: aliases(), aliases: aliases(),
deps: deps(), deps: deps(),
test_coverage: [tool: ExCoveralls] test_coverage: [
tool: ExCoveralls
],
releases: [
plausible: [
include_executables_for: [:unix],
applications: [plausible: :permanent],
steps: [:assemble, :tar]
]
]
] ]
end end
@ -20,7 +30,16 @@ defmodule Plausible.MixProject do
def application do def application do
[ [
mod: {Plausible.Application, []}, mod: {Plausible.Application, []},
extra_applications: [:logger, :sentry, :runtime_tools, :timex, :ua_inspector, :ref_inspector, :bamboo] extra_applications: [
:logger,
:sentry,
:runtime_tools,
:timex,
:ua_inspector,
:ref_inspector,
:bamboo,
:bamboo_smtp
]
] ]
end end
@ -33,7 +52,8 @@ defmodule Plausible.MixProject do
# Type `mix help deps` for examples and options. # Type `mix help deps` for examples and options.
defp deps do defp deps do
[ [
{:browser, "~> 0.4.3"}, # remove # remove
{:browser, "~> 0.4.3"},
{:bcrypt_elixir, "~> 2.0"}, {:bcrypt_elixir, "~> 2.0"},
{:cors_plug, "~> 1.5"}, {:cors_plug, "~> 1.5"},
{:ecto_sql, "~> 3.0"}, {:ecto_sql, "~> 3.0"},
@ -47,17 +67,20 @@ defmodule Plausible.MixProject do
{:phoenix_pubsub, "~> 1.1"}, {:phoenix_pubsub, "~> 1.1"},
{:plug_cowboy, "~> 2.0"}, {:plug_cowboy, "~> 2.0"},
{:postgrex, ">= 0.0.0"}, {:postgrex, ">= 0.0.0"},
{:poison, "~> 3.1"}, # Used in paddle_api, can remove #  Used in paddle_api, can remove
{:poison, "~> 3.1"},
{:ref_inspector, "~> 1.3"}, {:ref_inspector, "~> 1.3"},
{:timex, "~> 3.6"}, {:timex, "~> 3.6"},
{:ua_inspector, "~> 0.18"}, {:ua_inspector, "~> 0.18"},
{:bamboo, "~> 1.3"}, {:bamboo, "~> 1.3"},
{:bamboo_postmark, "~> 0.5"}, {:bamboo_postmark, "~> 0.5"},
{:bamboo_smtp, "~> 2.1.0"},
{:sentry, "~> 7.0"}, {:sentry, "~> 7.0"},
{:httpoison, "~> 1.4"}, {:httpoison, "~> 1.4"},
{:ex_machina, "~> 2.3", only: :test}, {:ex_machina, "~> 2.3", only: :test},
{:excoveralls, "~> 0.10", only: :test}, {:excoveralls, "~> 0.10", only: :test},
{:double, "~> 0.7.0", only: :test}, {:double, "~> 0.7.0", only: :test},
{:junit_formatter, "~> 3.1", only: [:test]},
{:joken, "~> 2.0"}, {:joken, "~> 2.0"},
{:php_serializer, "~> 0.9.0"}, {:php_serializer, "~> 0.9.0"},
{:csv, "~> 2.3"}, {:csv, "~> 2.3"},

View File

@ -1,11 +1,10 @@
%{ %{
"bamboo": {:hex, :bamboo, "1.4.0", "7b9201c49a843e4802061cf45692405b2c00efcf1cebf8b7b64f015ead072392", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b9cad03bf38c7f37b6308876039355665b6ce09fefb46dc529cef4def912cffa"}, "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"},
"bamboo_postmark": {:hex, :bamboo_postmark, "0.6.0", "429ee3153497e2f1081f8741242450be13cdca52e2c56166e8eda5ebfcb23c0a", [:mix], [{:bamboo, ">= 1.2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:hackney, ">= 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "badb3c7677440f641d920e0900ff0cd13c01d517e76562aa5c00e560f46f36ba"}, "bamboo_postmark": {:hex, :bamboo_postmark, "0.6.0", "429ee3153497e2f1081f8741242450be13cdca52e2c56166e8eda5ebfcb23c0a", [:mix], [{:bamboo, ">= 1.2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:hackney, ">= 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "badb3c7677440f641d920e0900ff0cd13c01d517e76562aa5c00e560f46f36ba"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"}, "bamboo_smtp": {:hex, :bamboo_smtp, "2.1.0", "4be58f3c51d9f7875dc169ae58a1d2f08e5b718bf3895f70d130548c0598f422", [:mix], [{:bamboo, "~> 1.2", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.15.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "0aad00ef93d0e0c83a0e1ca6998fea070c8a720a990fbda13ce834136215ee49"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"},
"browser": {:hex, :browser, "0.4.4", "bd6436961a6b2299c6cb38d0e49761c1161d869cd0db46369cef2bf6b77c3665", [:mix], [{:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d476ca309d4a4b19742b870380390aabbcb323c1f6f8745e2da2dfd079b4f8d7"}, "browser": {:hex, :browser, "0.4.4", "bd6436961a6b2299c6cb38d0e49761c1161d869cd0db46369cef2bf6b77c3665", [:mix], [{:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d476ca309d4a4b19742b870380390aabbcb323c1f6f8745e2da2dfd079b4f8d7"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"clickhouse_ecto": {:git, "git@github.com:appodeal/clickhouse_ecto.git", "c4fa1c3d2b73e4be698e205ad6e9ace22ac23f7d", []},
"clickhousex": {:git, "https://github.com/atlas-forks/clickhousex.git", "e010c4eaa6cb6b659e44790a3bea2ec7703ceb31", []}, "clickhousex": {:git, "https://github.com/atlas-forks/clickhousex.git", "e010c4eaa6cb6b659e44790a3bea2ec7703ceb31", []},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
@ -17,50 +16,50 @@
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"double": {:hex, :double, "0.7.0", "a7ee4c3488a0acc6d2ad9b69b6c7d3ddf3da2b54488d0f7c2d6ceb3a995887ca", [:mix], [], "hexpm", "f0c387a2266b4452da7bab03598feec11aef8b2acab061ea947dae81bb257329"}, "double": {:hex, :double, "0.7.0", "a7ee4c3488a0acc6d2ad9b69b6c7d3ddf3da2b54488d0f7c2d6ceb3a995887ca", [:mix], [], "hexpm", "f0c387a2266b4452da7bab03598feec11aef8b2acab061ea947dae81bb257329"},
"ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"}, "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"},
"ecto_sql": {:hex, :ecto_sql, "3.4.2", "3d842665a81ba2137b62aa70151afe81dae44824cd09b2076a255937ab4e2dc9", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f2b064102467e1525314a464b6fea0707ff28ee132a15006727ccf51b73492ff"}, "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
"ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"},
"excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"}, "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
"extwitter": {:hex, :extwitter, "0.11.0", "9472e19f1711bc60bc7efa594353164532475d7c47ea9f1bb66d4faa889b079e", [:mix], [{:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"},
"gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
"joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
"junit_formatter": {:hex, :junit_formatter, "3.1.0", "3f69c61c5413750f9c45e367d77aabbeac9b395acf478d8e70b4ee9d1989c709", [:mix], [], "hexpm", "da52401a93f711fc4f77ffabdda68f9a16fcad5d96f5fce4ae606ab1d73b72f4"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"myxql": {:hex, :myxql, "0.4.0", "d95582db9e4b4707eb3a6a7002b8869a5240247931775f82d811ad450ca06503", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.3", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "27a7ebaf7822cf7c89ea796371e70b942c8ec5e2c4eedcb8cd8e043f014014ad"},
"nanoid": {:hex, :nanoid, "2.0.2", "f3f7b4bf103ab6667f22beb00b6315825ee3f30100dd2c93d534e5c02164e857", [:mix], [], "hexpm", "3095cb1fac7bbc78843a8ccd99f1af375d0da1d3ebaa8552e846b73438c0c44f"}, "nanoid": {:hex, :nanoid, "2.0.2", "f3f7b4bf103ab6667f22beb00b6315825ee3f30100dd2c93d534e5c02164e857", [:mix], [], "hexpm", "3095cb1fac7bbc78843a8ccd99f1af375d0da1d3ebaa8552e846b73438c0c44f"},
"oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm", "9374f4302045321874cccdc57eb975893643bd69c3b22bf1312dab5f06e5788e"}, "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm", "9374f4302045321874cccdc57eb975893643bd69c3b22bf1312dab5f06e5788e"},
"parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"}, "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"phoenix": {:hex, :phoenix, "1.4.16", "2cbbe0c81e6601567c44cc380c33aa42a1372ac1426e3de3d93ac448a7ec4308", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "856cc1a032fa53822737413cf51aa60e750525d7ece7d1c0576d90d7c0f05c24"}, "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
"phoenix_html": {:hex, :phoenix_html, "2.14.1", "7dabafadedb552db142aacbd1f11de1c0bbaa247f90c449ca549d5e30bbc66b4", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "536d5200ad37fecfe55b3241d90b7a8c3a2ca60cd012fc065f776324fa9ab0a9"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.1", "274a4b07c4adbdd7785d45a8b0bb57634d0b4f45b18d2c508b26c0344bd59b8f", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "41b4103a2fa282cfd747d377233baf213c648fdcc7928f432937676532490eee"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"},
"php_serializer": {:hex, :php_serializer, "0.9.2", "59c5fd6bd3096671fd89358fb8229341ac7423b50ad8d45a15213b02ea2edab2", [:mix], [], "hexpm", "34eb835a460944f7fc216773b363c02e7dcf8ac0390c9e9ccdbd92b31a7ca59a"}, "php_serializer": {:hex, :php_serializer, "0.9.2", "59c5fd6bd3096671fd89358fb8229341ac7423b50ad8d45a15213b02ea2edab2", [:mix], [], "hexpm", "34eb835a460944f7fc216773b363c02e7dcf8ac0390c9e9ccdbd92b31a7ca59a"},
"plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"}, "plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"},
"plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"}, "plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"}, "postgrex": {:hex, :postgrex, "0.15.4", "5d691c25fc79070705a2ff0e35ce0822b86a0ee3c6fdb7a4fb354623955e1aed", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "306515b9d975fcb2478dc337a1d27dc3bf8af7cd71017c333fe9db3a3d211b0a"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"ref_inspector": {:hex, :ref_inspector, "1.3.0", "a02b89647440d084f2867ecece7a99895bcd4683482397fe086508bb22a165f3", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "d2069ae6b371112ac696a3cd116fd1e08d5726249b8d1357f377e67f0716cc10"}, "ref_inspector": {:hex, :ref_inspector, "1.3.1", "bb0489a4c4299dcd633f2b7a60c41a01f5590789d0b28225a60be484e1fbe777", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "3172eb1b08e5c69966f796e3fe0e691257546fa143a5eb0ecc18a6e39b233854"},
"sentry": {:hex, :sentry, "7.2.4", "b5bc90b594d40c2e653581e797a5fd2fdf994f2568f6bd66b7fa4971598be8d5", [:mix], [{:hackney, "~> 1.8 or 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "4ee4d368b5013076afcc8b73ed028bdc8ee9db84ea987e3591101e194c1fc24b"}, "sentry": {:hex, :sentry, "7.2.4", "b5bc90b594d40c2e653581e797a5fd2fdf994f2568f6bd66b7fa4971598be8d5", [:mix], [{:hackney, "~> 1.8 or 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "4ee4d368b5013076afcc8b73ed028bdc8ee9db84ea987e3591101e194c1fc24b"},
"siphash": {:hex, :siphash, "3.2.0", "ec03fd4066259218c85e2a4b8eec4bb9663bc02b127ea8a0836db376ba73f2ed", [:make, :mix], [], "hexpm", "ba3810701c6e95637a745e186e8a4899087c3b079ba88fb8f33df054c3b0b7c3"}, "siphash": {:hex, :siphash, "3.2.0", "ec03fd4066259218c85e2a4b8eec4bb9663bc02b127ea8a0836db376ba73f2ed", [:make, :mix], [], "hexpm", "ba3810701c6e95637a745e186e8a4899087c3b079ba88fb8f33df054c3b0b7c3"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"}, "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"}, "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
"ua_inspector": {:hex, :ua_inspector, "0.20.0", "01939baf5706f7d6c2dc0affbbd7f5e14309ba43ebf8967aa6479ee2204f23bc", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.0", [hex: :poolboy, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "30e8623b9f55e7d58be12fc2afd50be8792ec14192c289701d3cc93ad6027f26"}, "ua_inspector": {:hex, :ua_inspector, "0.20.0", "01939baf5706f7d6c2dc0affbbd7f5e14309ba43ebf8967aa6479ee2204f23bc", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.0", [hex: :poolboy, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "30e8623b9f55e7d58be12fc2afd50be8792ec14192c289701d3cc93ad6027f26"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
"yamerl": {:hex, :yamerl, "0.7.0", "e51dba652dce74c20a88294130b48051ebbbb0be7d76f22de064f0f3ccf0aaf5", [:rebar3], [], "hexpm", "cb5a4481e2e2ad36db83bd9962153e1a9208e2b2484185e33fc2caac6a50b108"}, "yamerl": {:hex, :yamerl, "0.8.0", "8214cfe16bbabe5d1d6c14a14aea11c784b9a21903dd6a7c74f8ce180adae5c7", [:rebar3], [], "hexpm", "010634477bf9c208a0767dcca89116c2442cf0b5e87f9c870f85cd1c3e0c2aab"},
} }

View File

@ -1,11 +1,12 @@
defmodule Plausible.Repo.Migrations.AddPublicSites do defmodule Plausible.Repo.Migrations.AddPublicSites do
use Ecto.Migration use Ecto.Migration
@host Application.get_env(:plausible, :url, :host)
def change do def change do
alter table(:sites) do alter table(:sites) do
add :public, :boolean, null: false, default: false add :public, :boolean, null: false, default: false
end end
execute "update sites set public=true where domain='plausible.io'" execute "update sites set public=true where domain='#{@host}'"
end end
end end

6
rel/env.bat.eex Normal file
View File

@ -0,0 +1,6 @@
@echo off
rem Set the release to work across nodes. If using the long name format like
rem the one below (my_app@127.0.0.1), you need to also uncomment the
rem RELEASE_DISTRIBUTION variable below. Must be "sname", "name" or "none".
rem set RELEASE_DISTRIBUTION=name
rem set RELEASE_NODE=<%= @release.name %>@127.0.0.1

18
rel/env.sh.eex Normal file
View File

@ -0,0 +1,18 @@
#!/bin/sh
# Sets and enables heart (recommended only in daemon mode)
# case $RELEASE_COMMAND in
# daemon*)
# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND"
# export HEART_COMMAND
# export ELIXIR_ERL_OPTIONS="-heart"
# ;;
# *)
# ;;
# esac
# Set the release to work across nodes. If using the long name format like
# the one below (my_app@127.0.0.1), you need to also uncomment the
# RELEASE_DISTRIBUTION variable below. Must be "sname", "name" or "none".
# export RELEASE_DISTRIBUTION=name
# export RELEASE_NODE=<%= @release.name %>@127.0.0.1

7
rel/overlays/createdb.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
# Creates the database if needed
BIN_DIR=`dirname "$0"`
${BIN_DIR}/bin/plausible eval Plausible.Release.createdb

6
rel/overlays/migrate.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
# starts the db migration
BIN_DIR=`dirname "$0"`
${BIN_DIR}/bin/plausible eval Plausible.Release.migrate

5
rel/overlays/rollback.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
BIN_DIR=`dirname "$0"`
${BIN_DIR}/bin/plausible eval Plausible.Release.rollback

5
rel/overlays/seed.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
BIN_DIR=`dirname "$0"`
${BIN_DIR}/bin/plausible eval Plausible.Release.seed

11
rel/vm.args.eex Normal file
View File

@ -0,0 +1,11 @@
## Customize flags given to the VM: http://erlang.org/doc/man/erl.html
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
## Number of dirty schedulers doing IO work (file, sockets, and others)
##+SDio 5
## Increase number of concurrent ports/sockets
##+Q 65536
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10

View File

@ -1,4 +1,5 @@
{:ok, _} = Application.ensure_all_started(:ex_machina) {:ok, _} = Application.ensure_all_started(:ex_machina)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
Plausible.Test.ClickhouseSetup.run() Plausible.Test.ClickhouseSetup.run()
ExUnit.start() ExUnit.start()
Application.ensure_all_started(:double) Application.ensure_all_started(:double)