Commit 0e3cbcb1 authored by Mikko Ahlroth's avatar Mikko Ahlroth

Initial commit

# Used by "mix format"
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
# The directory Mix will write compiled artifacts to.
# If you run "mix test --cover", coverage assets end up here.
# The directory Mix downloads your dependencies sources to.
# Where 3rd-party dependencies like ExDoc output generated docs.
# Ignore .fetch files in case you like to edit your project deps locally.
# If the VM crashes, it generates a dump, let's ignore it too.
# Also ignore archive artifacts (built via "mix").
# Ignore package tarball (built via "mix").
# ElixirLS cache
# Tilastokeskus
**TODO: Add description**
## Installation
If [available in Hex](, the package can be installed
by adding `tilastokeskus` to your list of dependencies in `mix.exs`:
def deps do
{:tilastokeskus, "~> 0.1.0"}
Documentation can be generated with [ExDoc](
and published on [HexDocs]( Once published, the docs can
be found at [](
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure your application as:
# config :tilastokeskus, key: :value
# and access this configuration in your application as:
# Application.get_env(:tilastokeskus, :key)
# You can also configure a 3rd-party app:
# config :logger, level: :info
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
# import_config "#{Mix.env}.exs"
config :ecto, json_library: Jason
config :tilastokeskus,
ecto_repos: [Tilastokeskus.Arkisto.Repo]
config :tilastokeskus, Tilastokeskus.Arkisto.Repo,
url: "ecto://tilastokeskus:tilastokeskus@localhost/tilastokeskus",
types: Tilastokeskus.Arkisto.PostgresTypes
[] ++ Ecto.Adapters.Postgres.extensions(),
json: Jason
defmodule Tilastokeskus.Arkisto.Repo do
use Ecto.Repo,
otp_app: :tilastokeskus,
adapter: Ecto.Adapters.Postgres
def init(_type, config) do
config =
case System.get_env("DATABASE_URL") do
nil -> config
url -> Keyword.put(config, :url, url)
{:ok, config}
defmodule Tilastokeskus.Arkisto.Schemas.Events do
@moduledoc """
An event that can be a pageview, or some other action by the user or backend.
require Tilastokeskus.Arkisto.Schemas.EventCommons
use Ecto.Schema
schema "events" do
defmodule Tilastokeskus.Arkisto.Schemas.EventCommons do
@moduledoc """
Common stuff for both events and pageviews.
@doc """
Common fields for both events and pageviews, to avoid duplication.
defmacro fields() do
quote do
Ecto.Schema.field(:at, :utc_datetime)
Ecto.Schema.field(:type, :string)
Ecto.Schema.field(:addr, Tilastokeskus.Arkisto.Types.Inet)
# Extra information
Ecto.Schema.field(:extra, :map)
# Has the row been scrubbed of identifying information?
Ecto.Schema.field(:scrubbed, :boolean)
Ecto.Schema.belongs_to(:session, Tilastokeskus.Arkisto.Schemas.Session, type: Ecto.UUID)
defmodule Tilastokeskus.Arkisto.Schemas.PageView do
@moduledoc """
A page view of a certain page.
require Tilastokeskus.Arkisto.Schemas.EventCommons
use Ecto.Schema
schema "events" do
# Full HTTP referrer
field(:referrer, :string)
# Referrer without query string
field(:referrer_noq, :string)
# Just the domain of the referrer
field(:referrer_domain, :string)
# Full user agent string
field(:ua, :string)
# User agent scrubbed to only contain the user agent name and version
field(:scrubbed_ua, :string)
field(:scrubbed_ua_version, :string)
# Screen size
field(:screen_w, :integer)
field(:screen_h, :integer)
# User's local timezone offset to UTC in minutes
field(:tz_offset, :integer)
# GeoIP calculated information
field(:loc_lat, :float)
field(:loc_lon, :float)
field(:loc_city, :string)
field(:loc_country, :string)
defmodule Tilastokeskus.Arkisto.Schemas.Session do
@moduledoc """
A single user session that can be used to track a user's actions on the site.
use Ecto.Schema
@primary_key {:id, :binary_id, autogenerate: true}
schema "sessions" do
field(:opened_at, :utc_datetime)
defmodule Tilastokeskus.Arkisto.Types.Inet do
@moduledoc """
Inet type for Ecto for storing IPs in PostgreSQL.
@behaviour Ecto.Type
@impl Ecto.Type
@doc """
Defines what internal database type is used.
def type(), do: :string
@impl Ecto.Type
@doc """
As we don't have any special casting rules, simply pass the value.
def cast(value), do: {:ok, value}
@impl Ecto.Type
@doc """
Loads the IP as Postgrex.INET structure from the database and coverts to a tuple.
def load(%Postgrex.INET{address: address}), do: {:ok, address}
@impl Ecto.Type
@doc """
Receives IP as a tuple and converts to Postgrex.INET structure. In case IP is not a tuple,
returns an error.
def dump(value) when is_tuple(value) do
{:ok, %Postgrex.INET{address: value}}
def dump(_), do: :error
defmodule Tilastokeskus do
defmodule Tilastokeskus.Application do
# See
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
port = (System.get_env("PORT") || "1971") |> String.to_integer()
# List all child processes to be supervised
children = [
{Tilastokeskus.Arkisto.Repo, []},
{Tilastokeskus.Router, [port: port]}
# See
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Tilastokeskus.Supervisor]
Supervisor.start_link(children, opts)
defmodule Tilastokeskus.Router do
use Ace.HTTP.Service, port: 1971, cleartext: true
use Raxx.Logger
use Raxx.Router, [
{_, Tilastokeskus.Routes.NotFound}
defmodule Tilastokeskus.Routes.NotFound do
use Raxx.Server
@impl Raxx.Server
def handle_request(_req, _state) do
|> set_header("content-type", "application/json")
|> set_body(Jason.encode!(%{error: "Route not found."}))
defmodule Tilastokeskus.MixProject do
use Mix.Project
def project do
app: :tilastokeskus,
version: "0.1.0",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
deps: deps()
# Run "mix help" to learn about applications.
def application do
extra_applications: [:logger],
mod: {Tilastokeskus.Application, []}
# Run "mix help deps" to learn about dependencies.
defp deps do
{:raxx, "~> 0.15.4"},
{:ace, "~> 0.16.5"},
{:postgrex, ">= 0.0.0"},
{:ecto, "~> 2.2"},
{:jason, "~> 1.0"}
"ace": {:hex, :ace, "0.16.5", "725f4511768bba7e083d3c93d8e499259101e49c0a9497252ce54a79a91e96e8", [:mix], [{:hpack, "~> 0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}, {:raxx, "~> 0.15.2", [hex: :raxx, repo: "hexpm", optional: false]}], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cookie": {:hex, :cookie, "0.1.1", "89438362ee0f0ed400e9f076d617d630f82d682e3fbcf767072a46a6e1ed5781", [:mix], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm"},
"jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
"raxx": {:hex, :raxx, "0.15.4", "62e4a487e55e9469d1ba2d5de590672167f51eaac310f027f3f871018cb6a9fc", [:mix], [{:cookie, "~> 0.1.0", [hex: :cookie, repo: "hexpm", optional: false]}], "hexpm"},
defmodule Tilastokeskus.Arkisto.Repo.Migrations.Initial do
use Ecto.Migration
def change do
create table(:sessions, primary_key: false) do
add(:id, :uuid, primary_key: true)
add(:opened_at, :timestamp, null: false)
create table(:events, primary_key: false) do
add(:id, :bigint, primary_key: true)
add(:at, :timestamp, null: false)
references("sessions", on_delete: :nilify_all, on_update: :update_all, type: :uuid)
add(:type, :string, null: false, default: "event")
add(:addr, :inet, null: true)
add(:extra, :jsonb, null: false, default: "{}")
add(:scrubbed, :boolean, null: false, default: false)
add(:referrer, :text, null: true)
add(:referrer_noq, :text, null: true)
add(:referrer_domain, :text, null: true)
add(:ua, :text, null: true)
add(:scrubbed_ua, :text, null: true)
add(:scrubbed_ua_version, :text, null: true)
add(:screen_w, :integer, null: true)
add(:screen_h, :integer, null: true)
add(:tz_offset, :integer, null: true)
add(:loc_lat, :"double precision", null: true)
add(:loc_lon, :"double precision", null: true)
add(:loc_city, :text, null: true)
add(:loc_country, :text, null: true)
create(index(:events, :session_id))
create(index(:events, ["at DESC"]))
create(index(:events, :type))
create(index(:events, :referrer_domain))
defmodule TilastokeskusTest do
use ExUnit.Case
doctest Tilastokeskus
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment