Compare commits

...

16 Commits

Author SHA1 Message Date
Marcin Kulik 11554512e9 Fix logic related to email uniqueness 7 years ago
Marcin Kulik a9f443766e Send HTML emails 7 years ago
Marcin Kulik a96e8518bc Simplify S3.open_file with "with" form 7 years ago
Marcin Kulik 8c9e9f4c03 Fix credo warnings 7 years ago
Marcin Kulik 39e6fb31c0 Add empty ecto migrations dir 7 years ago
Marcin Kulik 0a195defe0 Fix aliases in .iex.exs 7 years ago
Marcin Kulik c25820677e Rename context Users -> Accounts 7 years ago
Marcin Kulik 8e37e17ca1 Merge pull request #281 from asciinema/phx13
Upgrade to Phoenix 1.3
7 years ago
Marcin Kulik 05d42b3f2f Fix another deprecation warning 7 years ago
Marcin Kulik cc60d4e04b Fix Docker build 7 years ago
Marcin Kulik 426ed5ddda Upgrade phoenix_ecto and timex_ecto 7 years ago
Marcin Kulik 857a45d310 Fix embed.js symlink 7 years ago
Marcin Kulik 5f2e91356a Fix deprecation warning 7 years ago
Marcin Kulik ea8d1c9b40 Update directory structure to Phoenix 1.3 layout 7 years ago
Marcin Kulik f249d4a276 Upgrade Phoenix to 1.3 7 years ago
Marcin Kulik 3b798f61c9 Serve embed script without redirect 7 years ago

@ -1 +1,3 @@
alias Asciinema.{Repo, Asciicasts, Asciicast, Users, User}
alias Asciinema.{Repo, Asciicasts, Accounts}
alias Asciinema.Asciicasts.Asciicast
alias Asciinema.Accounts.{User, ApiToken}

@ -132,23 +132,23 @@ RUN mix deps.get --only prod
# install brunch & co
COPY package.json /app/
RUN npm install
COPY assets/package.json /app/assets/
RUN cd assets && npm install
# compile assets with brunch and generate digest file
COPY brunch-config.js /app/
COPY web/static /app/web/static
RUN node_modules/brunch/bin/brunch build --production && mix phoenix.digest
COPY assets /app/assets
RUN cd assets && node_modules/brunch/bin/brunch build --production
RUN mix phoenix.digest
# add Elixir source files
COPY config/*.exs /app/config/
COPY lib/*.ex /app/lib
COPY lib/asciinema /app/lib/asciinema
COPY lib/asciinema_web /app/lib/asciinema_web
COPY priv/gettext /app/priv/gettext
COPY priv/repo /app/priv/repo
COPY web /app/web
# compile Elixir app

@ -1,90 +0,0 @@
// asciinema embedded player
(function() {
function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
function params(container, script) {
function format(name) {
var value = script.getAttribute('data-' + name);
if (value) {
return name + '=' + value;
}
}
var options = ['size', 'speed', 'autoplay', 'loop', 'theme', 't', 'preload', 'cols', 'rows'];
return '?' + options.map(format).filter(Boolean).join('&');
}
function locationFromString(string) {
var parser = document.createElement('a');
parser.href = string;
return parser;
}
function apiHostFromScript(script) {
var location = locationFromString(script.src);
return location.protocol + '//' + location.host;
}
function insertPlayer(script) {
// do not insert player if there's one already associated with this script
if (script.dataset.player) {
return;
}
var apiHost = apiHostFromScript(script);
var asciicastId = script.id.split('-')[1];
var container = document.createElement('div');
container.id = "asciicast-container-" + asciicastId;
container.className = 'asciicast';
container.style.display = 'block';
container.style.float = 'none';
container.style.overflow = 'hidden';
container.style.padding = 0;
container.style.margin = '20px 0';
insertAfter(script, container);
var iframe = document.createElement('iframe');
iframe.src = apiHost + "/a/" + asciicastId + '/embed' + params(container, script);
iframe.id = "asciicast-iframe-" + asciicastId;
iframe.name = "asciicast-iframe-" + asciicastId;
iframe.scrolling = "no";
iframe.setAttribute('allowFullScreen', 'true');
iframe.style.overflow = "hidden";
iframe.style.margin = 0;
iframe.style.border = 0;
iframe.style.display = "inline-block";
iframe.style.width = "100%";
iframe.style.float = "none";
iframe.style.visibility = "hidden";
iframe.onload = function() { this.style.visibility = 'visible' };
container.appendChild(iframe);
function receiveSize(e) {
if (e.origin === apiHost) {
var name = e.data[0];
var data = e.data[1];
var iframeWindow = iframe.contentWindow || iframe;
if (e.source == iframeWindow && name == 'asciicast:size') {
iframe.style.width = '' + data.width + 'px';
iframe.style.height = '' + data.height + 'px';
}
}
}
window.addEventListener("message", receiveSize, false);
script.dataset.player = container;
}
var scripts = document.querySelectorAll("script[id^='asciicast-']");
[].forEach.call(scripts, insertPlayer);
})();

@ -0,0 +1 @@
../../../assets/static/js/embed.js

1
assets/.gitignore vendored

@ -0,0 +1 @@
/node_modules

@ -14,8 +14,8 @@ exports.config = {
// To change the order of concatenation of files, explicitly mention here
order: {
before: [
"web/static/vendor/js/jquery-2.2.4.min.js",
"web/static/vendor/js/bootstrap.js"
"vendor/js/jquery-2.2.4.min.js",
"vendor/js/bootstrap.js"
]
}
},
@ -23,19 +23,19 @@ exports.config = {
joinTo: "css/app.css",
order: {
before: [
"web/static/vendor/css/bootstrap.css",
"web/static/css/source-sans-pro.css",
"web/static/css/base.sass",
"web/static/css/header.sass",
"web/static/css/flash.sass",
"web/static/css/footer.sass",
"web/static/css/home.sass",
"web/static/css/asciicasts.sass",
"web/static/css/users.sass",
"web/static/css/preview.sass",
"web/static/css/player.sass",
"web/static/css/contributing.sass",
"web/static/css/simple-layout.sass",
"vendor/css/bootstrap.css",
"css/source-sans-pro.css",
"css/base.sass",
"css/header.sass",
"css/flash.sass",
"css/footer.sass",
"css/home.sass",
"css/asciicasts.sass",
"css/users.sass",
"css/preview.sass",
"css/player.sass",
"css/contributing.sass",
"css/simple-layout.sass",
]
}
},
@ -46,34 +46,31 @@ exports.config = {
conventions: {
// This option sets where we should place non-css and non-js assets in.
// By default, we set this to "/web/static/assets". Files in this directory
// By default, we set this to "/assets/static". Files in this directory
// will be copied to `paths.public`, which is "priv/static" by default.
assets: /^(web\/static\/assets)/
assets: /^(static)/
},
// Phoenix paths configuration
paths: {
// Dependencies and current project directories to watch
watched: [
"web/static",
"test/static"
],
watched: ["static", "css", "js", "vendor"],
// Where to compile files to
public: "priv/static"
public: "../priv/static"
},
// Configure your plugins
plugins: {
babel: {
// Do not use ES6 compiler in vendor code
ignore: [/web\/static\/vendor/]
ignore: [/vendor/]
}
},
modules: {
autoRequire: {
"js/app.js": ["web/static/js/app"]
"js/app.js": ["js/app"]
}
},

@ -6,16 +6,16 @@
"watch": "brunch watch --stdin"
},
"dependencies": {
"phoenix": "file:deps/phoenix",
"phoenix_html": "file:deps/phoenix_html"
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html"
},
"devDependencies": {
"babel-brunch": "~6.0.0",
"brunch": "2.7.4",
"clean-css-brunch": "~2.0.0",
"babel-brunch": "6.0.6",
"brunch": "2.10.7",
"clean-css-brunch": "2.10.0",
"css-brunch": "~2.0.0",
"sass-brunch": "^2.6.3",
"javascript-brunch": "~2.0.0",
"uglify-js-brunch": "~2.0.1"
"uglify-js-brunch": "2.1.1"
}
}

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 768 B

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 731 B

@ -0,0 +1,90 @@
// asciinema embedded player
(function() {
function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
function params(container, script) {
function format(name) {
var value = script.getAttribute('data-' + name);
if (value) {
return name + '=' + value;
}
}
var options = ['size', 'speed', 'autoplay', 'loop', 'theme', 't', 'preload', 'cols', 'rows'];
return '?' + options.map(format).filter(Boolean).join('&');
}
function locationFromString(string) {
var parser = document.createElement('a');
parser.href = string;
return parser;
}
function apiHostFromScript(script) {
var location = locationFromString(script.src);
return location.protocol + '//' + location.host;
}
function insertPlayer(script) {
// do not insert player if there's one already associated with this script
if (script.dataset.player) {
return;
}
var apiHost = apiHostFromScript(script);
var asciicastId = script.id.split('-')[1];
var container = document.createElement('div');
container.id = "asciicast-container-" + asciicastId;
container.className = 'asciicast';
container.style.display = 'block';
container.style.float = 'none';
container.style.overflow = 'hidden';
container.style.padding = 0;
container.style.margin = '20px 0';
insertAfter(script, container);
var iframe = document.createElement('iframe');
iframe.src = apiHost + "/a/" + asciicastId + '/embed' + params(container, script);
iframe.id = "asciicast-iframe-" + asciicastId;
iframe.name = "asciicast-iframe-" + asciicastId;
iframe.scrolling = "no";
iframe.setAttribute('allowFullScreen', 'true');
iframe.style.overflow = "hidden";
iframe.style.margin = 0;
iframe.style.border = 0;
iframe.style.display = "inline-block";
iframe.style.width = "100%";
iframe.style.float = "none";
iframe.style.visibility = "hidden";
iframe.onload = function() { this.style.visibility = 'visible' };
container.appendChild(iframe);
function receiveSize(e) {
if (e.origin === apiHost) {
var name = e.data[0];
var data = e.data[1];
var iframeWindow = iframe.contentWindow || iframe;
if (e.source == iframeWindow && name == 'asciicast:size') {
iframe.style.width = '' + data.width + 'px';
iframe.style.height = '' + data.height + 'px';
}
}
}
window.addEventListener("message", receiveSize, false);
script.dataset.player = container;
}
var scripts = document.querySelectorAll("script[id^='asciicast-']");
[].forEach.call(scripts, insertPlayer);
})();

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

@ -10,10 +10,10 @@ config :asciinema,
ecto_repos: [Asciinema.Repo]
# Configures the endpoint
config :asciinema, Asciinema.Endpoint,
config :asciinema, AsciinemaWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: System.get_env("SECRET_KEY_BASE"),
render_errors: [view: Asciinema.ErrorView, accepts: ~w(html json)],
render_errors: [view: AsciinemaWeb.ErrorView, accepts: ~w(html json)],
pubsub: [name: Asciinema.PubSub,
adapter: Phoenix.PubSub.PG2]

@ -6,24 +6,24 @@ use Mix.Config
# The watchers configuration can be used to run external
# watchers to your application. For example, we use it
# with brunch.io to recompile .js and .css sources.
config :asciinema, Asciinema.Endpoint,
config :asciinema, AsciinemaWeb.Endpoint,
http: [port: 4000],
debug_errors: true,
code_reloader: true,
check_origin: false,
secret_key_base: System.get_env("SECRET_KEY_BASE") || "60BnXnzGGwwiZj91YA9XYKF9BCiM7lQ/1um8VXcWWLSdUp9OcPZV6YnQv7eFTYSY",
watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
cd: Path.expand("../", __DIR__)]]
cd: Path.expand("../assets", __DIR__)]]
# Watch static and templates for browser reloading.
config :asciinema, Asciinema.Endpoint,
config :asciinema, AsciinemaWeb.Endpoint,
live_reload: [
patterns: [
~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
~r{priv/gettext/.*(po)$},
~r{web/views/.*(ex)$},
~r{web/templates/.*(eex|md)$}
~r{lib/asciinema_web/views/.*(ex)$},
~r{lib/asciinema_web/templates/.*(eex|md)$}
]
]

@ -11,12 +11,12 @@ use Mix.Config
# containing the digested version of static files. This
# manifest is generated by the mix phoenix.digest task
# which you typically run after static files are built.
config :asciinema, Asciinema.Endpoint,
config :asciinema, AsciinemaWeb.Endpoint,
http: [port: {:system, "PORT"}],
url: [scheme: System.get_env("URL_SCHEME") || "https",
host: System.get_env("URL_HOST") || "asciinema.org",
port: String.to_integer(System.get_env("URL_PORT") || "443")],
cache_static_manifest: "priv/static/manifest.json"
cache_static_manifest: "priv/static/cache_manifest.json"
# Do not print debug messages in production
config :logger, level: :info
@ -26,7 +26,7 @@ config :logger, level: :info
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
# config :asciinema, Asciinema.Endpoint,
# config :asciinema, AsciinemaWeb.Endpoint,
# ...
# url: [host: "example.com", port: 443],
# https: [port: 443,
@ -40,7 +40,7 @@ config :logger, level: :info
# We also recommend setting `force_ssl`, ensuring no data is
# ever sent via http, always redirecting to https:
#
# config :asciinema, Asciinema.Endpoint,
# config :asciinema, AsciinemaWeb.Endpoint,
# force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.
@ -55,7 +55,7 @@ config :logger, level: :info
# Alternatively, you can configure exactly which server to
# start per endpoint:
#
# config :asciinema, Asciinema.Endpoint, server: true
# config :asciinema, AsciinemaWeb.Endpoint, server: true
#
config :asciinema, Asciinema.Repo,

@ -2,7 +2,7 @@ use Mix.Config
# We don't run a server during test. If one is required,
# you can enable the server option below.
config :asciinema, Asciinema.Endpoint,
config :asciinema, AsciinemaWeb.Endpoint,
http: [port: 4001],
secret_key_base: "ssecretkeybasesecretkeybasesecretkeybasesecretkeybaseecretkeybase",
server: false

@ -0,0 +1,6 @@
class MakeEmailUnique < ActiveRecord::Migration
def change
remove_index :users, name: "index_users_on_email"
add_index :users, :email, unique: true
end
end

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170728221839) do
ActiveRecord::Schema.define(version: 20170803171814) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -81,7 +81,7 @@ ActiveRecord::Schema.define(version: 20170728221839) do
end
add_index "users", ["auth_token"], name: "index_users_on_auth_token", using: :btree
add_index "users", ["email"], name: "index_users_on_email", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["provider", "uid"], name: "index_users_on_provider_and_uid", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree

@ -44,7 +44,7 @@ server {
proxy_redirect off;
}
location ~ ^/a/[^.]+\.(json|gif)$ {
location ~ ^/a/[^.]+\.(js|json|gif)$ {
try_files $uri $uri/index.html $uri.html @phoenix;
}

@ -1,7 +1,8 @@
defmodule Asciinema.Users do
defmodule Asciinema.Accounts do
import Ecto.Query, warn: false
import Ecto, only: [assoc: 2]
alias Asciinema.{Repo, User, ApiToken, Asciicasts, Email, Mailer, Auth}
alias Asciinema.Accounts.{User, ApiToken}
alias Asciinema.{Repo, Asciicasts, Email, Mailer}
def create_asciinema_user!() do
attrs = %{username: "asciinema",
@ -79,25 +80,25 @@ defmodule Asciinema.Users do
{:ok, url}
end
defp signup_url(email) do
token = Phoenix.Token.sign(Asciinema.Endpoint, "signup", email)
Asciinema.Router.Helpers.users_url(Asciinema.Endpoint, :new, t: token)
def signup_url(email) do
token = Phoenix.Token.sign(AsciinemaWeb.Endpoint, "signup", email)
AsciinemaWeb.Router.Helpers.users_url(AsciinemaWeb.Endpoint, :new, t: token)
end
defp login_url(%User{id: id, last_login_at: last_login_at}) do
def login_url(%User{id: id, last_login_at: last_login_at}) do
last_login_at = last_login_at && Timex.to_unix(last_login_at)
token = Phoenix.Token.sign(Asciinema.Endpoint, "login", {id, last_login_at})
Asciinema.Router.Helpers.session_url(Asciinema.Endpoint, :new, t: token)
token = Phoenix.Token.sign(AsciinemaWeb.Endpoint, "login", {id, last_login_at})
AsciinemaWeb.Router.Helpers.session_url(AsciinemaWeb.Endpoint, :new, t: token)
end
@login_token_max_age 15 * 60 # 15 minutes
alias Phoenix.Token
alias Asciinema.Endpoint
alias AsciinemaWeb.Endpoint
def verify_signup_token(token) do
with {:ok, email} <- Token.verify(Endpoint, "signup", token, max_age: @login_token_max_age),
{:ok, %User{} = user} <- User.signup_changeset(%{email: email}) |> Repo.insert do
{:ok, %User{} = user} <- Repo.insert(User.signup_changeset(%{email: email})) do
{:ok, user}
else
{:error, :invalid} ->
@ -124,11 +125,6 @@ defmodule Asciinema.Users do
end
end
def log_in(conn, %User{} = user) do
user = user |> User.login_changeset |> Repo.update!
Auth.login(conn, user)
end
def authenticate(api_token) do
q = from u in User,
join: at in ApiToken,

@ -1,6 +1,7 @@
defmodule Asciinema.ApiToken do
use Asciinema.Web, :model
alias Asciinema.{ApiToken, User}
defmodule Asciinema.Accounts.ApiToken do
use Ecto.Schema
import Ecto.Changeset
alias Asciinema.Accounts.{ApiToken, User}
schema "api_tokens" do
field :token, :string
@ -8,7 +9,7 @@ defmodule Asciinema.ApiToken do
timestamps(inserted_at: :created_at)
belongs_to :user, Asciinema.User
belongs_to :user, User
end
@uuid4 ~r/\A[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\z/

@ -1,6 +1,7 @@
defmodule Asciinema.User do
use Asciinema.Web, :model
alias Asciinema.User
defmodule Asciinema.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
alias Asciinema.Accounts.User
@valid_email_re ~r/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i
@ -16,8 +17,8 @@ defmodule Asciinema.User do
timestamps(inserted_at: :created_at)
has_many :asciicasts, Asciinema.Asciicast
has_many :api_tokens, Asciinema.ApiToken
has_many :asciicasts, Asciinema.Asciicasts.Asciicast
has_many :api_tokens, Asciinema.Accounts.ApiToken
end
def changeset(struct, params \\ %{}) do
@ -37,6 +38,7 @@ defmodule Asciinema.User do
|> create_changeset(attrs)
|> cast(attrs, [:email])
|> validate_required([:email])
|> unique_constraint(:email, name: "index_users_on_email")
end
def login_changeset(user) do

@ -1,4 +1,4 @@
defmodule Asciinema do
defmodule Asciinema.Application do
use Application
# See http://elixir-lang.org/docs/stable/elixir/Application.html
@ -13,7 +13,7 @@ defmodule Asciinema do
# Start the Ecto repository
supervisor(Asciinema.Repo, []),
# Start the endpoint when the application starts
supervisor(Asciinema.Endpoint, []),
supervisor(AsciinemaWeb.Endpoint, []),
# Start your own worker by calling: Asciinema.Worker.start_link(arg1, arg2, arg3)
# worker(Asciinema.Worker, [arg1, arg2, arg3]),
:poolboy.child_spec(:worker, Asciinema.PngGenerator.A2png.poolboy_config(), []),
@ -27,11 +27,4 @@ defmodule Asciinema do
opts = [strategy: :one_for_one, name: Asciinema.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
Asciinema.Endpoint.config_change(changed, removed)
:ok
end
end

@ -1,6 +1,8 @@
defmodule Asciinema.Asciicast do
use Asciinema.Web, :model
alias Asciinema.{User, Asciicast}
defmodule Asciinema.Asciicasts.Asciicast do
use Ecto.Schema
import Ecto.Changeset
alias Asciinema.Accounts.User
alias Asciinema.Asciicasts.Asciicast
alias Asciinema.PngGenerator.PngParams
@default_png_scale 2

@ -1,7 +1,7 @@
defmodule Asciinema.Asciicasts do
import Ecto.Query, warn: false
alias Asciinema.{Repo, Asciicast, FileStore, StringUtils, Vt}
alias Asciinema.Asciicasts.{SnapshotUpdater, FramesGenerator}
alias Asciinema.{Repo, FileStore, StringUtils, Vt}
alias Asciinema.Asciicasts.{Asciicast, SnapshotUpdater, FramesGenerator}
def get_asciicast!(id) when is_integer(id) do
Repo.get!(Asciicast, id)
@ -164,9 +164,9 @@ defmodule Asciinema.Asciicasts do
header = File.open!(path, [:read], fn file -> IO.binread(file, 2) end)
case header do
<<0x1f,0x8b>> -> # gzip
<<0x1f, 0x8b>> -> # gzip
File.open!(path, [:read, :compressed])
<<0x42,0x5a>> -> # bzip
<<0x42, 0x5a>> -> # bzip
{:ok, tmp_path} = Briefly.create()
{_, 0} = System.cmd("sh", ["-c", "bzip2 -d -k -c #{path} >#{tmp_path}"])
File.open!(tmp_path, [:read])

@ -1,5 +1,5 @@
defmodule Asciinema.Asciicasts.FramesGenerator do
alias Asciinema.Asciicast
alias Asciinema.Asciicasts.Asciicast
@doc "Generates frames file for given asciicast"
@callback generate_frames(asciicast :: %Asciicast{}) :: :ok | {:error, term}

@ -1,5 +1,5 @@
defmodule Asciinema.Asciicasts.FramesGenerator.Sidekiq do
alias Asciinema.Asciicast
alias Asciinema.Asciicasts.Asciicast
alias Asciinema.SidekiqClient
def generate_frames(%Asciicast{id: id}) do

@ -1,5 +1,5 @@
defmodule Asciinema.Asciicasts.SnapshotUpdater do
alias Asciinema.Asciicast
alias Asciinema.Asciicasts.Asciicast
@doc "Generates poster for given asciicast"
@callback update_snapshot(asciicast :: %Asciicast{}) :: :ok | {:error, term}

@ -1,5 +1,6 @@
defmodule Asciinema.Asciicasts.SnapshotUpdater.Exq do
alias Asciinema.{Repo, Asciicasts, Asciicast}
alias Asciinema.{Repo, Asciicasts}
alias Asciinema.Asciicasts.Asciicast
def update_snapshot(%Asciicast{id: id}) do
{:ok, _jid} = Exq.enqueue(Exq, "default", __MODULE__, [id])

@ -1,5 +1,6 @@
defmodule Asciinema.Asciicasts.SnapshotUpdater.Sync do
alias Asciinema.{Asciicast, Asciicasts}
alias Asciinema.Asciicasts
alias Asciinema.Asciicasts.Asciicast
def update_snapshot(%Asciicast{} = asciicast) do
{:ok, _} = Asciicasts.update_snapshot(asciicast)

@ -1,5 +1,5 @@
defmodule Asciinema.Email do
use Bamboo.Phoenix, view: Asciinema.EmailView
use Bamboo.Phoenix, view: AsciinemaWeb.EmailView
import Bamboo.Email
def signup_email(email_address, signup_url) do
@ -7,6 +7,7 @@ defmodule Asciinema.Email do
|> to(email_address)
|> subject("Welcome to #{instance_hostname()}")
|> render("signup.text", signup_url: signup_url)
|> render("signup.html", signup_url: signup_url)
end
def login_email(email_address, login_url) do
@ -14,12 +15,14 @@ defmodule Asciinema.Email do
|> to(email_address)
|> subject("Login request")
|> render("login.text", login_url: login_url)
|> render("login.html", login_url: login_url)
end
defp base_email do
new_email()
|> from({"asciinema", from_address()})
|> put_header("Reply-To", reply_to_address())
|> put_html_layout({AsciinemaWeb.LayoutView, "email.html"})
end
defp from_address do

@ -1,7 +1,6 @@
defmodule Asciinema.FileStore.Local do
use Asciinema.FileStore
import Plug.Conn
alias Plug.MIME
def put_file(dst_path, src_local_path, _content_type, _compress \\ false) do
full_dst_path = base_path() <> dst_path
@ -24,7 +23,7 @@ defmodule Asciinema.FileStore.Local do
defp do_serve_file(conn, path) do
conn
|> put_resp_header("content-type", MIME.path(path))
|> put_resp_header("content-type", MIME.from_path(path))
|> send_file(200, base_path() <> path)
|> halt
end

@ -5,7 +5,7 @@ defmodule Asciinema.FileStore.S3 do
def put_file(dst_path, src_local_path, content_type, compress \\ false) do
{body, opts} = if compress do
body = File.read!(src_local_path) |> :zlib.gzip
body = src_local_path |> File.read! |> :zlib.gzip
opts = [{:content_type, content_type}, {:content_encoding, "gzip"}]
{body, opts}
else
@ -36,23 +36,20 @@ defmodule Asciinema.FileStore.S3 do
end
def open_file(path, function \\ nil) do
response = S3.get_object(bucket(), base_path() <> path) |> make_request
response = bucket() |> S3.get_object(base_path() <> path) |> make_request
case response do
{:ok, %{headers: headers, body: body}} ->
body =
case List.keyfind(headers, "Content-Encoding", 0) do
{"Content-Encoding", "gzip"} -> :zlib.gunzip(body)
_ -> body
end
if function do
File.open(body, [:ram, :binary, :read], function)
else
File.open(body, [:ram, :binary, :read])
with {:ok, %{headers: headers, body: body}} <- response do
body =
case List.keyfind(headers, "Content-Encoding", 0) do
{"Content-Encoding", "gzip"} -> :zlib.gunzip(body)
_ -> body
end
{:error, reason} ->
{:error, reason}
if function do
File.open(body, [:ram, :binary, :read], function)
else
File.open(body, [:ram, :binary, :read])
end
end
end

@ -1,5 +1,5 @@
defmodule Asciinema.PngGenerator do
alias Asciinema.Asciicast
alias Asciinema.Asciicasts.Asciicast
alias Asciinema.PngGenerator.PngParams
@doc "Generates PNG image from asciicast and returns path to it"

@ -1,13 +1,13 @@
defmodule Asciinema.PngGenerator.A2png do
@behaviour Asciinema.PngGenerator
use GenServer
alias Asciinema.Asciicast
alias Asciinema.Asciicasts.Asciicast
alias Asciinema.PngGenerator.PngParams
@pool_name :worker
@acquire_timeout 5000
@a2png_timeout 30000
@result_timeout 35000
@a2png_timeout 30_000
@result_timeout 35_000
def generate(%Asciicast{} = asciicast, %PngParams{} = png_params) do
{:ok, tmp_dir_path} = Briefly.create(directory: true)

@ -65,7 +65,7 @@ defmodule Asciinema.Vt.Worker do
end
defp send_cmd(port, cmd, data \\ %{}) do
json = Map.put(data, :cmd, cmd) |> Poison.encode!
json = data |> Map.put(:cmd, cmd) |> Poison.encode!
true = Port.command(port, "#{json}\n")
end
end

@ -1,12 +1,12 @@
defmodule Asciinema.Web do
defmodule AsciinemaWeb do
@moduledoc """
A module that keeps using definitions for controllers,
views and so on.
This can be used in your application as:
use Asciinema.Web, :controller
use Asciinema.Web, :view
use AsciinemaWeb, :controller
use AsciinemaWeb, :view
The definitions below will be executed for every view,
controller, etc, so keep them short and clean, focused
@ -16,35 +16,25 @@ defmodule Asciinema.Web do
below.
"""
def model do
quote do
use Ecto.Schema
@timestamps_opts inserted_at: :created_at
import Ecto
import Ecto.Changeset
import Ecto.Query
end
end
def controller do
quote do
use Phoenix.Controller
use Phoenix.Controller, namespace: AsciinemaWeb
alias Asciinema.Repo
import Ecto
import Ecto.Query
import Asciinema.Router.Helpers
import Asciinema.Router.Helpers.Extra
import Asciinema.Gettext
import Asciinema.Rails.Flash
import AsciinemaWeb.Router.Helpers
import AsciinemaWeb.Router.Helpers.Extra
import AsciinemaWeb.Gettext
import AsciinemaWeb.Rails.Flash
end
end
def view do
quote do
use Phoenix.View, root: "web/templates"
use Phoenix.View, root: "lib/asciinema_web/templates",
namespace: AsciinemaWeb
# Import convenience functions from controllers
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
@ -52,10 +42,10 @@ defmodule Asciinema.Web do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import Asciinema.Router.Helpers
import Asciinema.Router.Helpers.Extra
import Asciinema.ErrorHelpers
import Asciinema.Gettext
import AsciinemaWeb.Router.Helpers
import AsciinemaWeb.Router.Helpers.Extra
import AsciinemaWeb.ErrorHelpers
import AsciinemaWeb.Gettext
end
end
@ -73,7 +63,7 @@ defmodule Asciinema.Web do
alias Asciinema.Repo
import Ecto
import Ecto.Query
import Asciinema.Gettext
import AsciinemaWeb.Gettext
end
end

@ -1,9 +1,10 @@
defmodule Asciinema.Auth do
defmodule AsciinemaWeb.Auth do
import Plug.Conn
alias Asciinema.{Repo, User}
alias Asciinema.Accounts.User
alias Asciinema.Repo
@user_key "warden.user.user.key"
@one_year_in_secs 31557600
@one_year_in_secs 31_557_600
def init(opts) do
opts
@ -18,10 +19,12 @@ defmodule Asciinema.Auth do
assign(conn, :current_user, user)
end
def login(conn, %User{id: id, auth_token: auth_token} = user) do
def log_in(conn, %User{} = user) do
user = user |> User.login_changeset |> Repo.update!
conn
|> put_session(@user_key, id)
|> put_resp_cookie("auth_token", auth_token, max_age: @one_year_in_secs)
|> put_session(@user_key, user.id)
|> put_resp_cookie("auth_token", user.auth_token, max_age: @one_year_in_secs)
|> assign(:current_user, user)
end

@ -1,4 +1,4 @@
defmodule Asciinema.UserSocket do
defmodule AsciinemaWeb.UserSocket do
use Phoenix.Socket
## Channels
@ -30,7 +30,7 @@ defmodule Asciinema.UserSocket do
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# Asciinema.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
# AsciinemaWeb.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
def id(_socket), do: nil

@ -1,7 +1,8 @@
defmodule Asciinema.Api.AsciicastController do
use Asciinema.Web, :controller
import Asciinema.Auth, only: [get_basic_auth: 1, put_basic_auth: 3]
alias Asciinema.{Asciicasts, Users, User}
defmodule AsciinemaWeb.Api.AsciicastController do
use AsciinemaWeb, :controller
import AsciinemaWeb.Auth, only: [get_basic_auth: 1, put_basic_auth: 3]
alias Asciinema.{Asciicasts, Accounts}
alias Asciinema.Accounts.User
plug :parse_v0_params
plug :authenticate
@ -61,7 +62,7 @@ defmodule Asciinema.Api.AsciicastController do
defp authenticate(conn, _opts) do
with {username, api_token} <- get_basic_auth(conn),
{:ok, %User{} = user} <- Users.get_user_with_api_token(api_token, username) do
{:ok, %User{} = user} <- Accounts.get_user_with_api_token(api_token, username) do
assign(conn, :current_user, user)
else
_otherwise ->

@ -1,5 +1,5 @@
defmodule Asciinema.AsciicastAnimationController do
use Asciinema.Web, :controller
defmodule AsciinemaWeb.AsciicastAnimationController do
use AsciinemaWeb, :controller
alias Asciinema.Asciicasts
def show(conn, %{"id" => id}) do

@ -0,0 +1,14 @@
defmodule AsciinemaWeb.AsciicastEmbedController do
use AsciinemaWeb, :controller
@max_age 60
def show(conn, _params) do
path = Application.app_dir(:asciinema, "priv/static/js/embed.js")
conn
|> put_resp_content_type("application/javascript")
|> put_resp_header("cache-control", "public, max-age=#{@max_age}")
|> send_file(200, path)
end
end

@ -1,6 +1,7 @@
defmodule Asciinema.AsciicastFileController do
use Asciinema.Web, :controller
alias Asciinema.{Asciicasts, Asciicast}
defmodule AsciinemaWeb.AsciicastFileController do
use AsciinemaWeb, :controller
alias Asciinema.Asciicasts
alias Asciinema.Asciicasts.Asciicast
def show(conn, %{"id" => id} = params) do
asciicast = Asciicasts.get_asciicast!(id)

@ -1,9 +1,9 @@
defmodule Asciinema.AsciicastImageController do
use Asciinema.Web, :controller
alias Asciinema.{Asciicasts, Asciicast, PngGenerator}
alias Plug.MIME
defmodule AsciinemaWeb.AsciicastImageController do
use AsciinemaWeb, :controller
alias Asciinema.{Asciicasts, PngGenerator}
alias Asciinema.Asciicasts.Asciicast
@max_age 604800 # 7 days
@max_age 604_800 # 7 days
def show(conn, %{"id" => id} = _params) do
asciicast = Asciicasts.get_asciicast!(id)
@ -13,7 +13,7 @@ defmodule Asciinema.AsciicastImageController do
case PngGenerator.generate(asciicast, png_params) do
{:ok, png_path} ->
conn
|> put_resp_header("content-type", MIME.path(png_path))
|> put_resp_header("content-type", MIME.from_path(png_path))
|> put_resp_header("cache-control", "public, max-age=#{@max_age}")
|> send_file(200, png_path)
|> halt

@ -1,6 +1,6 @@
defmodule Asciinema.DocController do
use Asciinema.Web, :controller
alias Asciinema.{DocView, ErrorView}
defmodule AsciinemaWeb.DocController do
use AsciinemaWeb, :controller
alias AsciinemaWeb.{DocView, ErrorView}
@topics ["how-it-works", "getting-started", "installation", "usage", "config", "embedding", "faq"]

@ -1,6 +1,6 @@
defmodule Asciinema.LoginController do
use Asciinema.Web, :controller
alias Asciinema.Users
defmodule AsciinemaWeb.LoginController do
use AsciinemaWeb, :controller
alias Asciinema.Accounts
def new(conn, _params) do
render(conn, "new.html")
@ -9,7 +9,7 @@ defmodule Asciinema.LoginController do
def create(conn, %{"login" => %{"email" => email_or_username}}) do
email_or_username = String.trim(email_or_username)
case Users.send_login_email(email_or_username) do
case Accounts.send_login_email(email_or_username) do
{:ok, _url} ->
redirect(conn, to: login_path(conn, :sent))
{:error, :user_not_found} ->

@ -1,7 +1,9 @@
defmodule Asciinema.SessionController do
use Asciinema.Web, :controller
import Asciinema.UserView, only: [profile_path: 1]
alias Asciinema.{Users, User}
defmodule AsciinemaWeb.SessionController do
use AsciinemaWeb, :controller
import AsciinemaWeb.UserView, only: [profile_path: 1]
alias Asciinema.Accounts
alias AsciinemaWeb.Auth
alias Asciinema.Accounts.User
def new(conn, %{"t" => login_token}) do
conn
@ -13,7 +15,7 @@ defmodule Asciinema.SessionController do
end
def create(conn, %{"api_token" => api_token}) do
case Users.get_user_with_api_token(api_token) do
case Accounts.get_user_with_api_token(api_token) do
{:ok, user} ->
login_via_api_token(conn, user)
{:error, :token_invalid} ->
@ -31,10 +33,10 @@ defmodule Asciinema.SessionController do
login_token = get_session(conn, :login_token)
conn = delete_session(conn, :login_token)
case Users.verify_login_token(login_token) do
case Accounts.verify_login_token(login_token) do
{:ok, user} ->
conn
|> Users.log_in(user)
|> Auth.log_in(user)
|> put_rails_flash(:notice, "Welcome back!")
|> redirect_to_profile
{:error, :token_invalid} ->
@ -58,12 +60,12 @@ defmodule Asciinema.SessionController do
case {current_user, logging_user} do
{nil, %User{email: nil}} ->
conn
|> Users.log_in(logging_user)
|> Auth.log_in(logging_user)
|> put_rails_flash(:notice, "Welcome! Setting username and email will help you with logging in later.")
|> redirect_to_edit_profile
{nil, %User{}} ->
conn
|> Users.log_in(logging_user)
|> Auth.log_in(logging_user)
|> put_rails_flash(:notice, "Welcome back!")
|> redirect_to_profile
{%User{id: id, email: nil}, %User{id: id}} ->
@ -71,18 +73,18 @@ defmodule Asciinema.SessionController do
|> put_rails_flash(:notice, "Setting username and email will help you with logging in later.")
|> redirect_to_edit_profile
{%User{email: nil}, %User{email: nil}} ->
Users.merge!(current_user, logging_user)
Accounts.merge!(current_user, logging_user)
conn
|> put_rails_flash(:notice, "Setting username and email will help you with logging in later.")
|> redirect_to_edit_profile
{%User{email: nil}, %User{}} ->
Users.merge!(logging_user, current_user)
Accounts.merge!(logging_user, current_user)
conn
|> Users.log_in(logging_user)
|> Auth.log_in(logging_user)
|> put_rails_flash(:notice, "Recorder token has been added to your account.")
|> redirect_to_profile
{%User{}, %User{email: nil}} ->
Users.merge!(current_user, logging_user)
Accounts.merge!(current_user, logging_user)
conn
|> put_rails_flash(:notice, "Recorder token has been added to your account.")
|> redirect_to_profile

@ -1,6 +1,7 @@
defmodule Asciinema.UserController do
use Asciinema.Web, :controller
alias Asciinema.Users
defmodule AsciinemaWeb.UserController do
use AsciinemaWeb, :controller
alias Asciinema.Accounts
alias AsciinemaWeb.Auth
def new(conn, %{"t" => signup_token}) do
conn
@ -15,10 +16,10 @@ defmodule Asciinema.UserController do
signup_token = get_session(conn, :signup_token)
conn = delete_session(conn, :signup_token)
case Users.verify_signup_token(signup_token) do
case Accounts.verify_signup_token(signup_token) do
{:ok, user} ->
conn
|> Users.log_in(user)
|> Auth.log_in(user)
|> put_rails_flash(:info, "Welcome to asciinema!")
|> redirect(to: "/username/new")
{:error, :token_invalid} ->

@ -1,7 +1,7 @@
defmodule Asciinema.Endpoint do
defmodule AsciinemaWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :asciinema
socket "/socket", Asciinema.UserSocket
socket "/socket", AsciinemaWeb.UserSocket
# Serve at "/" the static files from "priv/static" directory.
#
@ -41,7 +41,7 @@ defmodule Asciinema.Endpoint do
key_digest: :sha,
serializer: Poison
plug Asciinema.TrailingFormat
plug AsciinemaWeb.TrailingFormat
plug Asciinema.Router
plug AsciinemaWeb.Router
end

@ -1,4 +1,4 @@
defmodule Asciinema.Gettext do
defmodule AsciinemaWeb.Gettext do
@moduledoc """
A module providing Internationalization with a gettext-based API.

@ -1,4 +1,4 @@
defmodule Asciinema.Rails.Flash do
defmodule AsciinemaWeb.Rails.Flash do
import Plug.Conn
def put_rails_flash(conn, key, value) do

@ -1,5 +1,5 @@
defmodule Asciinema.Router do
use Asciinema.Web, :router
defmodule AsciinemaWeb.Router do
use AsciinemaWeb, :router
pipeline :browser do
plug :accepts, ["html"]
@ -7,14 +7,25 @@ defmodule Asciinema.Router do
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Asciinema.Auth
plug AsciinemaWeb.Auth
end
pipeline :asciicast_embed_script do
plug :accepts, ["js"]
end
scope "/", AsciinemaWeb do
pipe_through :asciicast_embed_script
# rewritten by TrailingFormat from /a/123.js to /a/123/js
get "/a/:id/js", AsciicastEmbedController, :show
end
pipeline :asciicast_file do
plug :accepts, ["json"]
end
scope "/", Asciinema do
scope "/", AsciinemaWeb do
pipe_through :asciicast_file
# rewritten by TrailingFormat from /a/123.json to /a/123/json
@ -25,7 +36,7 @@ defmodule Asciinema.Router do
plug :accepts, ["png"]
end
scope "/", Asciinema do
scope "/", AsciinemaWeb do
pipe_through :asciicast_image
# rewritten by TrailingFormat from /a/123.png to /a/123/png
@ -36,14 +47,14 @@ defmodule Asciinema.Router do
plug :accepts, ["html"]
end
scope "/", Asciinema do
scope "/", AsciinemaWeb do
pipe_through :asciicast_animation
# rewritten by TrailingFormat from /a/123.gif to /a/123/gif
get "/a/:id/gif", AsciicastAnimationController, :show
end
scope "/", Asciinema do
scope "/", AsciinemaWeb do
pipe_through :browser # Use the default browser stack
get "/a/:id", AsciicastController, :show
@ -60,7 +71,7 @@ defmodule Asciinema.Router do
get "/connect/:api_token", SessionController, :create, as: :connect
end
scope "/api", Asciinema.Api, as: :api do
scope "/api", AsciinemaWeb.Api, as: :api do
post "/asciicasts", AsciicastController, :create
end
@ -70,30 +81,34 @@ defmodule Asciinema.Router do
# end
end
defmodule Asciinema.Router.Helpers.Extra do
alias Asciinema.Router.Helpers, as: H
defmodule AsciinemaWeb.Router.Helpers.Extra do
alias AsciinemaWeb.Router.Helpers, as: H
def user_path(_conn, :edit) do
"/user/edit"
end
def asciicast_file_download_path(conn, asciicast) do
H.asciicast_file_path(conn, :show, asciicast)
conn
|> H.asciicast_file_path(:show, asciicast)
|> String.replace_suffix("/json", ".json")
end
def asciicast_file_download_url(conn, asciicast) do
H.asciicast_file_url(conn, :show, asciicast)
conn
|> H.asciicast_file_url(:show, asciicast)
|> String.replace_suffix("/json", ".json")
end
def asciicast_image_download_path(conn, asciicast) do
H.asciicast_image_path(conn, :show, asciicast)
conn
|> H.asciicast_image_path(:show, asciicast)
|> String.replace_suffix("/png", ".png")
end
def asciicast_animation_download_path(conn, asciicast) do
H.asciicast_animation_path(conn, :show, asciicast)
conn
|> H.asciicast_animation_path(:show, asciicast)
|> String.replace_suffix("/gif", ".gif")
end
end

@ -0,0 +1,10 @@
<p>Hello,</p>
<p>Click the following link to log in to asciinema.org:</p>
<p><a href="<%= @login_url %>"><%= @login_url %></a></p>
<p>
<br>
If you did not initiate this request, just ignore this email. The request will expire shortly.
</p>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save