Support client version <= 0.9.7 in new upload endpoint

This commit is contained in:
Marcin Kulik 2017-06-15 22:25:04 +02:00
parent f34b9707f3
commit 515d486309
6 changed files with 159 additions and 14 deletions

View File

@ -20,8 +20,11 @@ defmodule Asciinema.Asciicasts do
Repo.one!(q) Repo.one!(q)
end end
def create_asciicast(user, %Plug.Upload{path: path, filename: filename} = upload) do def create_asciicast(user, params, user_agent \\ nil)
def create_asciicast(user, %Plug.Upload{path: path, filename: filename} = upload, user_agent) do
asciicast = %Asciicast{user_id: user.id, asciicast = %Asciicast{user_id: user.id,
user_agent: user_agent,
file: filename, file: filename,
private: user.asciicasts_private_by_default} private: user.asciicasts_private_by_default}
@ -31,8 +34,8 @@ defmodule Asciinema.Asciicasts do
{:ok, attrs} <- extract_attrs(attrs), {:ok, attrs} <- extract_attrs(attrs),
changeset = Asciicast.create_changeset(asciicast, attrs), changeset = Asciicast.create_changeset(asciicast, attrs),
{:ok, %Asciicast{} = asciicast} <- Repo.insert(changeset) do {:ok, %Asciicast{} = asciicast} <- Repo.insert(changeset) do
put_file(asciicast, upload) save_file(asciicast, :file, upload)
# TODO: generate snapshot and poster generate_poster(asciicast)
{:ok, asciicast} {:ok, asciicast}
else else
{:error, :invalid} -> {:error, :invalid} ->
@ -45,6 +48,30 @@ defmodule Asciinema.Asciicasts do
result result
end end
def create_asciicast(user, %{"meta" => attrs,
"stdout" => %Plug.Upload{filename: d_filename} = data,
"stdout_timing" => %Plug.Upload{filename: t_filename} = timing}, _user_agent) do
attrs = Map.put(attrs, "version", 0)
asciicast = %Asciicast{user_id: user.id,
stdout_data: d_filename,
stdout_timing: t_filename,
private: user.asciicasts_private_by_default}
changeset = Asciicast.create_changeset(asciicast, attrs)
{_, result} = Repo.transaction(fn ->
with {:ok, %Asciicast{} = asciicast} <- Repo.insert(changeset) do
save_file(asciicast, :stdout_data, data)
save_file(asciicast, :stdout_timing, timing)
generate_poster(asciicast)
{:ok, asciicast}
else
otherwise -> otherwise
end
end)
result
end
defp extract_attrs(%{"version" => 1} = attrs) do defp extract_attrs(%{"version" => 1} = attrs) do
attrs = %{version: attrs["version"], attrs = %{version: attrs["version"],
duration: attrs["duration"], duration: attrs["duration"],
@ -60,8 +87,12 @@ defmodule Asciinema.Asciicasts do
{:error, :unknown_format} {:error, :unknown_format}
end end
defp put_file(asciicast, %{path: tmp_file_path, content_type: content_type}) do defp save_file(asciicast, type, %{path: tmp_file_path, content_type: content_type}) do
file_store_path = Asciicast.json_store_path(asciicast) file_store_path = Asciicast.file_store_path(asciicast, type)
:ok = FileStore.put_file(file_store_path, tmp_file_path, content_type) :ok = FileStore.put_file(file_store_path, tmp_file_path, content_type)
end end
defp generate_poster(_asciicast) do
# TODO
end
end end

View File

@ -21,4 +21,12 @@ defmodule Asciinema.Auth do
_ -> nil _ -> nil
end end
end end
def put_basic_auth(conn, nil, nil) do
conn
end
def put_basic_auth(conn, username, password) do
auth = Base.encode64("#{username}:#{password}")
Conn.put_req_header(conn, "authorization", "Basic " <> auth)
end
end end

View File

@ -3,14 +3,48 @@ defmodule Asciinema.AsciicastsTest do
alias Asciinema.Asciicasts alias Asciinema.Asciicasts
describe "create_asciicast/2" do describe "create_asciicast/2" do
test "json file, v0 format, <= v0.9.7 client" do
user = fixture(:user)
params = %{"meta" => %{"command" => "/bin/bash",
"duration" => 11.146430015564,
"shell" => "/bin/zsh",
"terminal_columns" => 96,
"terminal_lines" => 26,
"terminal_type" => "screen-256color",
"title" => "bashing :)",
"uname" => "Linux 3.9.9-302.fc19.x86_64 #1 SMP Sat Jul 6 13:41:07 UTC 2013 x86_64"},
"stdout" => fixture(:upload, %{path: "0.9.7/stdout",
content_type: "application/octet-stream"}),
"stdout_timing" => fixture(:upload, %{path: "0.9.7/stdout.time",
content_type: "application/octet-stream"})}
{:ok, asciicast} = Asciicasts.create_asciicast(user, params, "a/user/agent")
assert asciicast.version == 0
assert asciicast.file == nil
assert asciicast.stdout_data == "stdout"
assert asciicast.stdout_timing == "stdout.time"
assert asciicast.command == "/bin/bash"
assert asciicast.duration == 11.146430015564
assert asciicast.shell == "/bin/zsh"
assert asciicast.terminal_type == "screen-256color"
assert asciicast.terminal_columns == 96
assert asciicast.terminal_lines == 26
assert asciicast.title == "bashing :)"
assert asciicast.uname == "Linux 3.9.9-302.fc19.x86_64 #1 SMP Sat Jul 6 13:41:07 UTC 2013 x86_64"
assert asciicast.user_agent == nil
end
test "json file, v1 format" do test "json file, v1 format" do
user = fixture(:user) user = fixture(:user)
upload = fixture(:upload, %{path: "1/asciicast.json"}) upload = fixture(:upload, %{path: "1/asciicast.json"})
{:ok, asciicast} = Asciicasts.create_asciicast(user, upload) {:ok, asciicast} = Asciicasts.create_asciicast(user, upload, "a/user/agent")
assert asciicast.version == 1 assert asciicast.version == 1
assert asciicast.file == "asciicast.json" assert asciicast.file == "asciicast.json"
assert asciicast.stdout_data == nil
assert asciicast.stdout_timing == nil
assert asciicast.command == "/bin/bash" assert asciicast.command == "/bin/bash"
assert asciicast.duration == 11.146430015564 assert asciicast.duration == 11.146430015564
assert asciicast.shell == "/bin/zsh" assert asciicast.shell == "/bin/zsh"
@ -19,7 +53,7 @@ defmodule Asciinema.AsciicastsTest do
assert asciicast.terminal_lines == 26 assert asciicast.terminal_lines == 26
assert asciicast.title == "bashing :)" assert asciicast.title == "bashing :)"
assert asciicast.uname == nil assert asciicast.uname == nil
# TODO assert asciicast.user_agent == "asciinema/1.0.0 gc/go1.3 jola-amd64" assert asciicast.user_agent == "a/user/agent"
end end
test "json file, v1 format (missing required data)" do test "json file, v1 format (missing required data)" do

View File

@ -17,6 +17,20 @@ defmodule Asciinema.Api.AsciicastControllerTest do
@asciicast_url ~r|^http://localhost:4001/a/[a-zA-Z0-9]{25}| @asciicast_url ~r|^http://localhost:4001/a/[a-zA-Z0-9]{25}|
describe ".create" do describe ".create" do
@tag token: nil
test "separate files (pre-v1 params), v0.9.7 client", %{conn: conn} do
asciicast = %{"meta" => fixture(:upload, %{path: "0.9.7/meta.json",
content_type: "application/json"}),
"stdout" => fixture(:upload, %{path: "0.9.7/stdout",
content_type: "application/octet-stream"}),
"stdout_timing" => fixture(:upload, %{path: "0.9.7/stdout.time",
content_type: "application/octet-stream"})}
conn = post conn, api_asciicast_path(conn, :create), %{"asciicast" => asciicast}
assert text_response(conn, 201) =~ @asciicast_url
assert List.first(get_resp_header(conn, "location")) =~ @asciicast_url
end
test "json file, v1 format", %{conn: conn} do test "json file, v1 format", %{conn: conn} do
upload = fixture(:upload, %{path: "1/asciicast.json"}) upload = fixture(:upload, %{path: "1/asciicast.json"})
conn = post conn, api_asciicast_path(conn, :create), %{"asciicast" => upload} conn = post conn, api_asciicast_path(conn, :create), %{"asciicast" => upload}

View File

@ -1,14 +1,25 @@
defmodule Asciinema.Api.AsciicastController do defmodule Asciinema.Api.AsciicastController do
use Asciinema.Web, :controller use Asciinema.Web, :controller
import Asciinema.Auth, only: [get_basic_auth: 1] import Asciinema.Auth, only: [get_basic_auth: 1, put_basic_auth: 3]
alias Asciinema.{Asciicasts, Users, User} alias Asciinema.{Asciicasts, Users, User}
plug :parse_v0_params
plug :authenticate plug :authenticate
def create(conn, %{"asciicast" => %Plug.Upload{} = upload}) do def create(conn, %{"asciicast" => %Plug.Upload{} = upload}) do
user = conn.assigns.current_user do_create(conn, upload)
end
def create(conn, %{"asciicast" => %{"meta" => %{},
"stdout" => %Plug.Upload{},
"stdout_timing" => %Plug.Upload{}} = asciicast_params}) do
do_create(conn, asciicast_params)
end
case Asciicasts.create_asciicast(user, upload) do defp do_create(conn, params) do
user = conn.assigns.current_user
user_agent = conn |> get_req_header("user-agent") |> List.first
case Asciicasts.create_asciicast(user, params, user_agent) do
{:ok, asciicast} -> {:ok, asciicast} ->
url = asciicast_url(conn, :show, asciicast) url = asciicast_url(conn, :show, asciicast)
conn conn
@ -30,6 +41,36 @@ defmodule Asciinema.Api.AsciicastController do
end end
end end
defp parse_v0_params(%Plug.Conn{params: %{"asciicast" => %{"meta" => %Plug.Upload{path: meta_path}}}} = conn, _) do
with {:ok, json} <- File.read(meta_path),
{:ok, attrs} <- Poison.decode(json),
{:ok, meta} <- extract_v0_attrs(attrs) do
conn
|> put_param(["asciicast", "meta"], meta)
|> put_basic_auth(attrs["username"], attrs["user_token"])
else
{:error, :invalid} ->
send_resp(conn, 400, "")
end
end
defp parse_v0_params(conn, _), do: conn
defp put_param(%Plug.Conn{params: params} = conn, path, value) do
params = put_in(params, path, value)
%{conn | params: params}
end
defp extract_v0_attrs(attrs) do
attrs = Map.merge(
Map.take(attrs, ["command", "duration", "shell", "title", "uname"]),
%{"terminal_columns" => get_in(attrs, ["term", "columns"]),
"terminal_lines" => get_in(attrs, ["term", "lines"]),
"terminal_type" => get_in(attrs, ["term", "type"])}
)
{:ok, attrs}
end
defp authenticate(conn, _opts) do defp authenticate(conn, _opts) do
with {username, api_token} <- get_basic_auth(conn), with {username, api_token} <- get_basic_auth(conn),
%User{} = user <- Users.get_user_with_api_token(username, api_token) do %User{} = user <- Users.get_user_with_api_token(username, api_token) do

View File

@ -24,6 +24,7 @@ defmodule Asciinema.Asciicast do
field :command, :string field :command, :string
field :shell, :string field :shell, :string
field :uname, :string field :uname, :string
field :user_agent, :string
timestamps(inserted_at: :created_at) timestamps(inserted_at: :created_at)
@ -55,11 +56,27 @@ defmodule Asciinema.Asciicast do
put_change(changeset, :secret_token, Crypto.random_token(25)) put_change(changeset, :secret_token, Crypto.random_token(25))
end end
def json_store_path(%__MODULE__{id: id, file: file}) when is_binary(file) do def json_store_path(%Asciicast{file: v} = asciicast) when is_binary(v) do
"asciicast/file/#{id}/#{file}" file_store_path(asciicast, :file)
end end
def json_store_path(%__MODULE__{id: id, stdout_frames: stdout_frames}) when is_binary(stdout_frames) do def json_store_path(%Asciicast{stdout_frames: v} = asciicast) when is_binary(v) do
"asciicast/stdout_frames/#{id}/#{stdout_frames}" file_store_path(asciicast, :stdout_frames)
end
def file_store_path(%Asciicast{id: id, file: fname}, :file) do
file_store_path(:file, id, fname)
end
def file_store_path(%Asciicast{id: id, stdout_frames: fname}, :stdout_frames) do
file_store_path(:stdout_frames, id, fname)
end
def file_store_path(%Asciicast{id: id, stdout_data: fname}, :stdout_data) do
file_store_path(:stdout_data, id, fname)
end
def file_store_path(%Asciicast{id: id, stdout_timing: fname}, :stdout_timing) do
file_store_path(:stdout_timing, id, fname)
end
def file_store_path(type, id, fname) when is_binary(fname) do
"asciicast/#{type}/#{id}/#{fname}"
end end
def snapshot_at(%Asciicast{snapshot_at: snapshot_at, duration: duration}) do def snapshot_at(%Asciicast{snapshot_at: snapshot_at, duration: duration}) do