Skip to content

Feat: Mds parser integration #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "mds-converter"]
path = mds-converter
url = [email protected]:markdown-docs/mds-converter.git
5 changes: 4 additions & 1 deletion lib/backend/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ defmodule Backend.Application do
# Start to serve requests, typically the last entry
BackendWeb.Endpoint,
Backend.Files.FileRegistry,
Backend.Files.FileManager
Backend.Files.FileManager,
# Backend.Parsers.Pool
# Backend.Parsers.MdsParser
{Task.Supervisor, name: Backend.TaskSupervisor}
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
4 changes: 4 additions & 0 deletions lib/backend/files/files.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ defmodule Backend.Files do
Repo.get(File, id)
end

def get_files() do
Repo.all(File)
end

def create_file(attrs \\ %{}) do
%File{}
|> File.changeset(attrs)
Expand Down
16 changes: 16 additions & 0 deletions lib/backend/parsers/mds_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Backend.Parsers.MdsParser do
@moduledoc """
MdsParser used to invoke haskell parser
"""

@parser_path Path.expand("mds-converter/runnable/parser", File.cwd!())

def parse(markdown) do
args = ["-c", markdown]

case System.cmd(@parser_path, args) do
{output, 0} -> {:ok, output}
{error, status} -> {:error, "Process failed with status #{status}: #{error}"}
end
end
end
46 changes: 46 additions & 0 deletions lib/backend_web/channels/file_channel.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule BackendWeb.FileChannel do
@moduledoc """
FileChannel used to interact with file's content
"""

use Phoenix.Channel

alias Backend.Files
alias Backend.Files.FileManager
alias Backend.Parsers.MdsParser
alias BackendWeb.PreviewChannel

require Logger

def join("file:" <> file_id, _params, socket) do
case Files.get_file(file_id) do
nil -> {:error, %{reason: "File with id " <> file_id <> " does not exist"}}
_ -> {:ok, assign(socket, :file_id, file_id)}
end
end

def handle_in("edit", %{"content" => content}, socket) do
file_id = socket.assigns.file_id

Logger.debug("HANDLED edit INCOMING ON file:#{file_id}")
Logger.debug(" Parameters: #{inspect(%{"content" => content})}")

FileManager.update_file_content(file_id, content)
broadcast!(socket, "update", %{content: content})

Task.Supervisor.start_child(Backend.TaskSupervisor, fn ->
case MdsParser.parse(content) do
{:ok, html} ->
PreviewChannel.send_preview(file_id, html)
Logger.debug("SENT preview BACK ON preview:#{file_id}")
# Limit log size
Logger.debug(" HTML Content: #{String.slice(html, 0..100)}...")

{:error, reason} ->
Logger.error("Parser error: #{inspect(reason)}")
end
end)

{:noreply, socket}
end
end
23 changes: 23 additions & 0 deletions lib/backend_web/channels/preview_channel.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule BackendWeb.PreviewChannel do
@moduledoc """
Websocket channel for each **converted** .mds file
"""
use Phoenix.Channel

def join("preview:" <> file_id, _params, socket) do
# store process PID in ETS (Erlang Term Storage) to listen to messages in topic
Phoenix.PubSub.subscribe(Backend.PubSub, "preview:#{file_id}")
{:ok, assign(socket, :file_id, file_id)}
end

# Отправка готового превью
def send_preview(file_id, html) do
# find all subscribed to preview topic procs in ETS (Erlang Term Storage) and send them message
Phoenix.PubSub.broadcast(Backend.PubSub, "preview:#{file_id}", {:preview, html})
end

def handle_info({:preview, html}, socket) do
push(socket, "preview", %{html: html})
{:noreply, socket}
end
end
27 changes: 0 additions & 27 deletions lib/backend_web/channels/room_channel.ex

This file was deleted.

2 changes: 1 addition & 1 deletion lib/backend_web/channels/user_socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ defmodule BackendWeb.UserSocket do
use Phoenix.Socket

## Каналы
# Это маршрут, на который будет подписан клиент
channel "file:*", BackendWeb.FileChannel
channel "preview:*", BackendWeb.PreviewChannel

# Здесь можно добавить авторизацию (опционально)
def connect(_params, socket, _connect_info) do
Expand Down
8 changes: 8 additions & 0 deletions lib/backend_web/controllers/file_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ defmodule BackendWeb.FileController do
end
end

def get_files(conn, _param) do
files = Files.get_files()

conn
|> put_status(200)
|> json(files)
end

def create_file(conn, %{"name" => name}) do
case Files.create_file(%{"name" => name, "content" => ""}) do
{:ok, file} ->
Expand Down
1 change: 1 addition & 0 deletions lib/backend_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ defmodule BackendWeb.Endpoint do
param_key: "request_logger",
cookie_key: "request_logger"

plug CORSPlug
plug Plug.RequestId
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]

Expand Down
1 change: 1 addition & 0 deletions lib/backend_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ defmodule BackendWeb.Router do
get "/", PageController, :home

get "/file/:id", FileController, :get_file
get "/file", FileController, :get_files
post "/file", FileController, :create_file
delete "/file/:id", FileController, :delete_file
end
Expand Down
1 change: 1 addition & 0 deletions mds-converter
Submodule mds-converter added at d6a541
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ defmodule Backend.MixProject do
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:propcheck, "~> 1.4.1", only: [:test]},
{:excoveralls, "~> 0.18", only: :test},
{:ecto_sqlite3, "~> 0.10.0"}
{:ecto_sqlite3, "~> 0.10.0"},
{:mock, "~> 0.3.8", only: :test},
{:cors_plug, "~> 3.0"}
]
end

Expand Down
4 changes: 4 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"},
Expand All @@ -23,8 +24,10 @@
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
Expand All @@ -37,6 +40,7 @@
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"},
"propcheck": {:hex, :propcheck, "1.4.1", "c12908dbe6f572032928548089b34ff9d40672d5d70f1562e3a9e9058d226cc9", [:mix], [{:libgraph, "~> 0.13", [hex: :libgraph, repo: "hexpm", optional: false]}, {:proper, "~> 1.4", [hex: :proper, repo: "hexpm", optional: false]}], "hexpm", "e1b088f574785c3c7e864da16f39082d5599b3aaf89086d3f9be6adb54464b19"},
"proper": {:hex, :proper, "1.4.0", "89a44b8c39d28bb9b4be8e4d715d534905b325470f2e0ec5e004d12484a79434", [:rebar3], [], "hexpm", "18285842185bd33efbda97d134a5cb5a0884384db36119fee0e3cfa488568cbb"},
Expand Down
2 changes: 1 addition & 1 deletion priv/repo/migrations/20241129212656_add_file.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Backend.Repo.Migrations.AddFile do
def change do
create table(:files) do
add :name, :string, null: false
add :content, :string
add :content, :string, size: 10000
end
end
end
38 changes: 38 additions & 0 deletions test/backend/mds_parser_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Backend.Parsers.MdsParserTest do
use ExUnit.Case, async: true

alias Backend.Parsers.MdsParser

@parser_path Path.expand("mds-converter/runnable/parser", File.cwd!())

setup do
# Проверяем, существует ли парсер
if File.exists?(@parser_path) do
:ok
else
{:error, "Parser executable not found at #{@parser_path}"}
end
end

test "parses valid content" do

Check failure on line 17 in test/backend/mds_parser_test.exs

View workflow job for this annotation

GitHub Actions / elixir_ci

test parses valid content (Backend.Parsers.MdsParserTest)
content = "# Test\n\nSome **bold** text"
assert {:ok, html} = MdsParser.parse(content)

assert html =~
"<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n</head>\n<body>\n<h1 id=\"0cbc6611f5540bd0809a388dc95a615b\">Test</h1>\n<p>Some <strong>bold</strong> text</p>\n\n</body>\n</html>\n"
end

test "handles invalid content gracefully" do

Check failure on line 25 in test/backend/mds_parser_test.exs

View workflow job for this annotation

GitHub Actions / elixir_ci

test handles invalid content gracefully (Backend.Parsers.MdsParserTest)
content = ""
assert {:ok, html} = MdsParser.parse(content)

# Даже пустой контент должен возвращать корректный HTML
assert html =~ "<body>"
end

test "handles parser errors" do

Check failure on line 33 in test/backend/mds_parser_test.exs

View workflow job for this annotation

GitHub Actions / elixir_ci

test handles parser errors (Backend.Parsers.MdsParserTest)
# Очень длинный ввод для потенциальной ошибки
content = :binary.copy("a", 1_000_000)
assert {:error, _} = MdsParser.parse(content)
end
end
3 changes: 3 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ Mix.Task.run("ecto.create", ["--quiet"])
Mix.Task.run("ecto.migrate", ["--quiet"])

ExUnit.start()
# Code.require_file("support/channel_case.ex", __DIR__)
Code.require_file("support/data_case.ex", __DIR__)
Code.require_file("support/conn_case.ex", __DIR__)
Ecto.Adapters.SQL.Sandbox.mode(Backend.Repo, :manual)
Loading