Browse Source

Update logic for syndication and photos.

This'll repair a bug with syndication links and make photo extraction
more normalized.
jackyalcine 6 months ago
parent
commit
af70a8fea7
Signed by: Jacky Alciné <yo@jacky.wtf> GPG Key ID: 537A4F904B15268D

+ 1
- 0
.formatter.exs View File

@@ -17,6 +17,7 @@
17 17
 # You should have received a copy of the GNU Affero General Public License
18 18
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 19
 [
20
+  import_deps: [:ecto, :phoenix],
20 21
   inputs: ["mix.exs", "{web,config,lib,test}/**/*.{ex,exs}"],
21 22
   line_length: 100
22 23
 ]

+ 0
- 1
.lvimrc View File

@@ -13,7 +13,6 @@ let s:command_prefix = 'docker-compose run ' .
13 13
       \ '-e MIX_ENV="test" ' .
14 14
       \ '-e OBJECT_STORAGE_BUCKET="koype-test" '.
15 15
       \ '-e TEST_HOST="http://koype_vim_test" ' .
16
-      \ '-e TEST_PORT=80 ' .
17 16
       \ '--name="koype_vim_test" '.
18 17
       \ '--use-aliases ' .
19 18
       \ '--rm ' .

+ 0
- 7
config/config.exs View File

@@ -42,13 +42,6 @@ config :koype, Koype.Repo,
42 42
   log: true,
43 43
   database: "priv/repo/db/#{Mix.env()}.db"
44 44
 
45
-config :logger, :console,
46
-  backends: [:console],
47
-  handle_sasl_reports: true,
48
-  format: "\n[$time] $metadata[$level] $message\n",
49
-  metadata: :all,
50
-  level: :info
51
-
52 45
 config :seedex,
53 46
   repo: Koype.Repo
54 47
 

+ 5
- 1
config/prod.exs View File

@@ -5,7 +5,11 @@ config :koype, Koype.Web.Endpoint,
5 5
   cache_static_manifest: "priv/static/cache_manifest.json",
6 6
   check_origin: true
7 7
 
8
-config :logger,
8
+config :logger, :console,
9
+  backends: [:console],
10
+  handle_sasl_reports: true,
11
+  format: "\n[$time] $metadata[$level] $message\n",
12
+  metadata: :all,
9 13
   level: :info,
10 14
   compile_time_purge_matching: [
11 15
     [level_lower_than: :info]

+ 1
- 5
config/test.exs View File

@@ -27,11 +27,7 @@ config :exvcr,
27 27
 config :hound,
28 28
   app_host: {:system, :string, "TEST_HOST", "localhost"},
29 29
   app_port: {:system, :integer, "TEST_PORT", 5001},
30
-  timeout: 30_000,
31
-  driver: "chrome_driver",
32
-  host: "http://webdriver",
33
-  path_prefix: "wd/hub/",
34
-  retries: 7,
30
+  host: "webdriver",
35 31
   port: 4444
36 32
 
37 33
 config :logger, level: :error

+ 2
- 32
docker-compose.yml View File

@@ -2,42 +2,13 @@
2 2
 version: "3.4"
3 3
 services:
4 4
   webdriver:
5
-    image: selenium/hub
6
-    networks:
7
-      - network
8
-      - grid
9
-    volumes:
10
-      - ./:/opt/koype/:z
11
-  chrome:
12
-    image: selenium/node-chrome-debug
5
+    image: selenium/standalone-chrome:3.141.59-mercury
13 6
     restart: always
14
-    depends_on:
15
-      - webdriver
16 7
     volumes:
17 8
       - ./:/opt/koype/:z
18
-    environment:
19
-      HUB_HOST: webdriver
20
-      HUB_PORT: 4444
21
-    links:
22
-      - webdriver
23
-    networks:
24
-      - network
25
-      - grid
26
-  firefox:
27
-    image: selenium/node-firefox-debug
28
-    restart: always
29
-    depends_on:
30
-      - webdriver
31
-    volumes:
32
-      - ./:/opt/koype/:z
33
-    environment:
34
-      HUB_HOST: webdriver
35
-      HUB_PORT: 4444
36
-    links:
37
-      - webdriver
9
+      - /dev/shm:/dev/shm
38 10
     networks:
39 11
       - network
40
-      - grid
41 12
   objectstorage:
42 13
     image: "minio/minio:RELEASE.2019-03-27T22-35-21Z"
43 14
     command: server /data
@@ -107,4 +78,3 @@ services:
107 78
 
108 79
 networks:
109 80
   network:
110
-  grid:

+ 12
- 18
lib/indieweb/micropub/content.ex View File

@@ -143,8 +143,16 @@ defmodule IndieWeb.Micropub.Content do
143 143
   def expand_property(name, value, model) when is_list(value) and name in ~w(photo video audio) do
144 144
     values =
145 145
       Enum.map(value, fn
146
-        data when is_binary(data) ->
147
-          expand_property(name, data, model)
146
+        media_path when is_binary(media_path) ->
147
+          if Koype.Storage.uri_from_object_store?(media_path) do
148
+            paths = Koype.Storage.extract_paths_from_uri(media_path)
149
+
150
+            %{
151
+              "uri" => paths
152
+            }
153
+          else
154
+            %{"uri" => %{"original" => media_path}}
155
+          end
148 156
 
149 157
         %{"path" => storage_path} = data ->
150 158
           paths =
@@ -152,7 +160,7 @@ defmodule IndieWeb.Micropub.Content do
152 160
               Koype.Storage.extract_paths_from_uri(storage_path)
153 161
             else
154 162
               module = Koype.Storage.module_for_type(name)
155
-              module.paths({storage_path, model})
163
+              module.paths({storage_path, model}) |> elem(1)
156 164
             end
157 165
 
158 166
           %{
@@ -166,20 +174,6 @@ defmodule IndieWeb.Micropub.Content do
166 174
     {:ok, values}
167 175
   end
168 176
 
169
-  def expand_property(name, storage_path, _)
170
-      when is_list(storage_path) and name in ~w(photo video audio) do
171
-    if Koype.Storage.uri_from_object_store?(URI.parse(storage_path)) do
172
-      Koype.Storage.extract_paths_from_uri(storage_path)
173
-    else
174
-      %{"uri" => storage_path, "alt" => nil}
175
-    end
176
-  end
177
-
178
-  def expand_property(name, value, model) when is_map(value) and name in ~w(photo video audio) do
179
-    Logger.debug("This property should be stored as a list with a single map.", property: name)
180
-    expand_property(name, [value], model)
181
-  end
182
-
183 177
   def expand_property(dt, value, _) when dt in ~w(start end) and is_binary(value) do
184 178
     options = [
185 179
       &Calendar.DateTime.Parse.rfc3339_utc/1,
@@ -239,7 +233,7 @@ defmodule IndieWeb.Micropub.Content do
239 233
   @spec expand_properties(properties :: map(), model :: any()) :: map() | {:error, any()}
240 234
   def expand_properties(properties, model) do
241 235
     Enum.reduce_while(properties, properties, fn {key, value}, acc ->
242
-      Logger.debug("Expanding property #{key} with #{inspect(value)} for #{inspect(model)}..")
236
+      Logger.debug("Expanding property...", key: key, value: inspect(value), model_id: model.id)
243 237
 
244 238
       case expand_property(key, value, model) do
245 239
         {:error, error} -> {:halt, {:error, error}}

+ 19
- 8
lib/indieweb/syndication.ex View File

@@ -17,6 +17,14 @@
17 17
 # You should have received a copy of the GNU Affero General Public License
18 18
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 19
 defmodule IndieWeb.Syndication do
20
+  @moduledoc """
21
+  Provide cross-platform posting support.
22
+
23
+  This keeps track of the methods and logic to handle syndication in Koype
24
+  and wraps it in a interface for the platform to use. This'll keep track 
25
+  of the status of said request as well.
26
+  """
27
+
20 28
   require Logger
21 29
   import Ecto.Query, only: [where: 2, order_by: 2, from: 2]
22 30
 
@@ -24,16 +32,18 @@ defmodule IndieWeb.Syndication do
24 32
   def use_count_of(target, status \\ :all)
25 33
 
26 34
   def use_count_of(target, :all) do
27
-    from(r in Koype.Repo.Syndication.Result, select: r.target_id, where: r.target_id == ^target.id)
28
-    |> Koype.Repo.count()
35
+    Koype.Repo.count(
36
+      from(r in Koype.Repo.Syndication.Result, select: r.target_id, where: r.target_id == ^target.id)
37
+    )
29 38
   end
30 39
 
31 40
   def use_count_of(target, status) do
32
-    from(r in Koype.Repo.Syndication.Result,
33
-      select: r.target_id,
34
-      where: r.target_id == ^target.id and r.status == ^status
41
+    Koype.Repo.count(
42
+      from(r in Koype.Repo.Syndication.Result,
43
+        select: r.target_id,
44
+        where: r.target_id == ^target.id and r.status == ^status
45
+      )
35 46
     )
36
-    |> Koype.Repo.count()
37 47
   end
38 48
 
39 49
   @spec targets(atom()) :: list()
@@ -75,10 +85,11 @@ defmodule IndieWeb.Syndication do
75 85
       )
76 86
 
77 87
       Koype.Repo.Syndication.Result.update(result, %{status: "completed"})
78
-      resp_headers = response.headers |> Map.new()
88
+      resp_headers = Map.new(response.headers)
79 89
 
80 90
       cond do
81
-        Map.take(resp_headers, ~w(content-type Content-Type))
91
+        resp_headers
92
+        |> Map.take(~w(content-type Content-Type))
82 93
         |> Map.values()
83 94
         |> List.flatten()
84 95
         |> List.first() =~ "json" ->

+ 2
- 2
lib/job.ex View File

@@ -62,12 +62,12 @@ defmodule Koype.Job do
62 62
 
63 63
     def on_success(args) do
64 64
       job = args[:job]
65
-      Logger.info("Job #{job.name()}##{job.id} was successful.", job_id: job.id)
65
+      Logger.info("Job was successful.", job_id: job.id, job_name: job.name())
66 66
     end
67 67
 
68 68
     def on_failure(args, error) do
69 69
       job = args[:job]
70
-      Logger.info("Job #{job.name()}##{job.id} failed.", job_id: job.id, error: inspect(error))
70
+      Logger.info("Job failed.", job_id: job.id, error: inspect(error), job_name: job.name())
71 71
     end
72 72
   end
73 73
 

+ 6
- 0
lib/page/parser.ex View File

@@ -28,6 +28,12 @@ defmodule Koype.Page.Parser do
28 28
 
29 29
   require Logger
30 30
 
31
+  def custom() do
32
+    Koype.Repo.PageParser
33
+    |> Koype.Repo.all()
34
+    |> Enum.map(&Map.take(&1, ~w(id name)a))
35
+  end
36
+
31 37
   def id_for_module(expected_module) do
32 38
     Enum.find_value(@parsers, fn {key, module} ->
33 39
       if module == expected_module do

+ 2
- 0
lib/repo.ex View File

@@ -110,6 +110,8 @@ defmodule Koype.Repo do
110 110
   def deleted(module), do: from(record in module, where: is_nil(record.deleted_at))
111 111
   def descending(module, key), do: order_by(module, desc: ^key)
112 112
   def ascending(module, key), do: order_by(module, asc: ^key)
113
+  def enabled(module), do: from(record in module, where: record.enabled == true)
114
+  def disabled(module), do: from(record in module, where: record.enabled == false)
113 115
 
114 116
   def count(module), do: aggregate(module, :count, :id)
115 117
 

+ 8
- 0
lib/repo/base.ex View File

@@ -47,6 +47,14 @@ defmodule Koype.Repo.Base do
47 47
         |> Koype.Repo.update()
48 48
       end
49 49
 
50
+      def disable(model) do
51
+        model |> change(enabled: false) |> Koype.Repo.update()
52
+      end
53
+
54
+      def enable(model) do
55
+        model |> change(enabled: true) |> Koype.Repo.update()
56
+      end
57
+
50 58
       if @route_view_method != nil do
51 59
         def get_uri(model), do: Koype.Repo.get_uri_for_record(model, @route_view_method)
52 60
         def get_path(model), do: Koype.Repo.get_path_for_record(model, @route_view_method)

+ 1
- 1
lib/repo/entry.ex View File

@@ -41,7 +41,7 @@ defmodule Koype.Repo.Entry do
41 41
     field(:post_status, :string, default: "published", null: false)
42 42
     field(:visibility, :string, default: "public", null: false)
43 43
 
44
-    many_to_many(:categories, Category, join_through: "entries_categories", on_delete: :nothing)
44
+    many_to_many(:categories, Category, join_through: "entries_categories", on_delete: :delete_all)
45 45
     has_many(:syndication_results, Koype.Repo.Entry.SyndicationResult)
46 46
 
47 47
     timestamps()

+ 13
- 4
lib/repo/entry/syndication_result.ex View File

@@ -19,7 +19,11 @@
19 19
 defmodule Koype.Repo.Entry.SyndicationResult do
20 20
   @moduledoc false
21 21
   require Ecto.Query
22
-  use Koype.Repo.Base
22
+
23
+  use Koype.Repo.Base,
24
+    lookup_columns: ~w(entry_id syndication_result_id)a
25
+
26
+  @primary_key false
23 27
 
24 28
   schema "entries_syndication_results" do
25 29
     belongs_to(:source, Koype.Repo.Entry, type: :binary_id, foreign_key: :entry_id)
@@ -50,15 +54,20 @@ defmodule Koype.Repo.Entry.SyndicationResult do
50 54
   def find(entry, target) do
51 55
     Koype.Repo.Entry.SyndicationResult
52 56
     |> Ecto.Query.join(:inner, [s], result in Koype.Repo.Syndication.Result)
53
-    |> Ecto.Query.join(:inner, [s], source in Koype.Repo.Entry)
54 57
     |> Ecto.Query.where(
55
-      [s, result, source],
56
-      result.target_id == ^target.id and source.id == ^entry.id
58
+      [s, result],
59
+      result.target_id == ^target.id and s.entry_id == ^entry.id
57 60
     )
58 61
     |> Ecto.Query.limit(1)
59 62
     |> Koype.Repo.one()
60 63
   end
61 64
 
65
+  def find_all(entry) do
66
+    Koype.Repo.Entry.SyndicationResult
67
+    |> Ecto.Query.where(entry_id: ^entry.id)
68
+    |> Ecto.Query.select([:entry_id, :syndication_result_id])
69
+  end
70
+
62 71
   @spec create(Koype.Repo.Entry.t(), Koype.Repo.Syndication.Target.t(), map()) ::
63 72
           {:ok, Koype.Repo.Syndication.Result.t()} | {:error, any()}
64 73
   def create(entry, target, args \\ %{}) do

+ 41
- 0
lib/repo/page_parser.ex View File

@@ -0,0 +1,41 @@
1
+defmodule Koype.Repo.PageParser do
2
+  use Koype.Repo.Base,
3
+    lookup_columns: ~w(id name url)a
4
+
5
+  @required_attrs ~w(url name method)a
6
+  @optional_attrs ~w(deleted_at enabled extraction_path)a
7
+
8
+  schema "page_parsers" do
9
+    field(:deleted_at, :naive_datetime, default: nil, null: true)
10
+    field(:enabled, :boolean, default: false)
11
+    field(:extraction_path, :string)
12
+    field(:method, :string, default: "get", null: false)
13
+    field(:url, :string, null: false)
14
+    field(:name, :string, null: false)
15
+
16
+    timestamps()
17
+  end
18
+
19
+  @doc false
20
+  def changeset(page_parser, attrs) do
21
+    page_parser
22
+    |> cast(attrs, @required_attrs ++ @optional_attrs)
23
+    |> validate_required(@required_attrs)
24
+    |> ensure_uuid(:id)
25
+  end
26
+
27
+  def create(params)
28
+
29
+  def create(params) when is_map(params) do
30
+    cs = changeset(%__MODULE__{}, params)
31
+
32
+    try do
33
+      case Koype.Repo.insert(cs) do
34
+        {:ok, _record} = ok -> ok
35
+        {:error, _cs} = err -> err
36
+      end
37
+    rescue
38
+      Sqlite.DbConnection.Error -> {:error, :parser_exists}
39
+    end
40
+  end
41
+end

+ 38
- 34
lib/template/format.ex View File

@@ -18,16 +18,16 @@
18 18
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 19
 
20 20
 defimpl Koype.Template.Formatter, for: Map do
21
-  def format(map),
22
-    do:
23
-      map
24
-      |> Enum.map(fn {k, v} -> {to_string(k), Koype.Template.Formatter.format(v)} end)
25
-      |> Enum.reject(fn {_, v} -> is_nil(v) end)
26
-      |> Map.new()
21
+  def format(map) do
22
+    map
23
+    |> Enum.map(fn {k, v} -> {to_string(k), Koype.Template.Formatter.format(v)} end)
24
+    |> Enum.reject(fn {_, v} -> is_nil(v) end)
25
+    |> Map.new()
26
+  end
27 27
 end
28 28
 
29 29
 defimpl Koype.Template.Formatter, for: DateTime do
30
-  def format(%DateTime{} = dt) do
30
+  def format(dt) do
31 31
     dt
32 32
     |> Calendar.DateTime.shift_zone!(Koype.Profile.timezone())
33 33
     |> Calendar.DateTime.Format.rfc3339(5)
@@ -35,15 +35,22 @@ defimpl Koype.Template.Formatter, for: DateTime do
35 35
 end
36 36
 
37 37
 defimpl Koype.Template.Formatter, for: List do
38
-  def format(list), do: Enum.map(list, &Koype.Template.format/1)
38
+  def format(list) do
39
+    Enum.map(list, &Koype.Template.format/1)
40
+  end
41
+end
42
+
43
+defimpl Koype.Template.Formatter, for: Any do
44
+  def format(value), do: value
39 45
 end
40 46
 
41 47
 defimpl Koype.Template.Formatter, for: Koype.Repo.Category do
42 48
   def format(%Koype.Repo.Category{} = category) do
43 49
     %{
44 50
       "id" => category.id,
45
-      "url" => Koype.Repo.Category.get_uri(category),
46
-      "name" => category.name
51
+      "name" => category.name,
52
+      "slug" => category.slug,
53
+      "url" => Koype.Repo.Category.get_uri(category)
47 54
     }
48 55
   end
49 56
 end
@@ -63,17 +70,24 @@ defimpl Koype.Template.Formatter, for: Koype.Repo.Entry do
63 70
       uri = entry |> Koype.Repo.Entry.get_uri()
64 71
       reloaded_entry = Koype.Repo.preload(entry, [:categories, :syndication_results])
65 72
 
66
-      entry
73
+      reloaded_entry
67 74
       |> Map.drop([:__meta__, :__struct__])
68 75
       |> Map.new()
69 76
       |> Map.put("uri", uri)
70 77
       |> Map.put("title", entry.name)
71 78
       |> Map.put("interaction_count", Koype.Repo.Webmention.count_of(uri))
72 79
       |> Map.put("json", props)
73
-      |> Map.put("class_type", do_fetch_class_type(entry))
74
-      |> Map.drop(~w(syndication_results categories)a)
75
-      |> Map.put("syndication", reloaded_entry.syndication_results)
76
-      |> Map.put("categories", reloaded_entry.categories)
80
+      |> Map.put("h_type", do_fetch_class_type(entry))
81
+      |> Map.drop(~w(updated_at)a)
82
+      |> Map.put_new_lazy("updated_at", fn ->
83
+        time_diff = DateTime.diff(reloaded_entry.updated_at, reloaded_entry.published_at, :second)
84
+
85
+        if time_diff > 2 do
86
+          reloaded_entry.updated_at
87
+        else
88
+          nil
89
+        end
90
+      end)
77 91
       |> Koype.Template.Formatter.format()
78 92
       |> Map.new()
79 93
     else
@@ -87,7 +101,7 @@ end
87 101
 defimpl Koype.Template.Formatter, for: Koype.Repo.Webmention do
88 102
   require Logger
89 103
 
90
-  def format(%Koype.Repo.Webmention{} = webmention) do
104
+  def format(webmention) do
91 105
     with({:ok, json} <- Koype.Repo.Webmention.Json.find(webmention)) do
92 106
       json |> Map.put("id", webmention.id)
93 107
     else
@@ -106,24 +120,16 @@ end
106 120
 defimpl Koype.Template.Formatter, for: Koype.Repo.Syndication.Target do
107 121
   require Logger
108 122
 
109
-  def format(result) do
110
-    result = Koype.Repo.preload(result, :target)
111
-
123
+  def format(target) do
112 124
     %{
113
-      "name" => result.target.name,
114
-      "uri" => result.uri,
115
-      "target" => result.target.id,
116
-      "active" => result.status == "completed"
125
+      "name" => target.name,
126
+      "id" => target.id,
127
+      "endpoint" => target.endpoint
117 128
     }
118
-    |> Enum.reject(fn {_, v} -> is_nil(v) end)
119 129
     |> Map.new()
120 130
   end
121 131
 end
122 132
 
123
-defimpl Koype.Template.Formatter, for: Any do
124
-  def format(value), do: value
125
-end
126
-
127 133
 defimpl Koype.Template.Formatter, for: Koype.Repo.Entry.SyndicationResult do
128 134
   require Logger
129 135
 
@@ -135,13 +141,11 @@ defimpl Koype.Template.Formatter, for: Koype.Repo.Entry.SyndicationResult do
135 141
       |> Koype.Repo.preload(:target)
136 142
 
137 143
     %{
138
-      "name" => result.target.name,
139
-      "endpoint" => result.target.endpoint,
140
-      "target" => result.target.id,
141
-      "uri" => result.result,
142
-      "status" => result.status
144
+      "target" => Koype.Template.Formatter.format(result.target),
145
+      "status" => result.status,
146
+      "url" => result.result
143 147
     }
144
-    |> Enum.reject(fn {_, v} -> is_nil(v) end)
148
+    |> Koype.Template.Formatter.format()
145 149
     |> Map.new()
146 150
   end
147 151
 end

+ 1
- 1
lib/web.ex View File

@@ -53,7 +53,7 @@ defmodule Koype.Web do
53 53
           Koype.Template.render_page(current_template, page_name, params)
54 54
         rescue
55 55
           e in [Koype.Template.Error] ->
56
-            Logger.warn("Ran into an error in the template: #{inspect(e)}.")
56
+            Logger.warn("Ran into an error in the template.", error: inspect(e))
57 57
             Logger.error(__STACKTRACE__)
58 58
 
59 59
             {:error,

+ 1
- 1
mix.exs View File

@@ -28,7 +28,6 @@ defmodule Koype.Mixfile do
28 28
       version: @version,
29 29
       elixir: "~> 1.7",
30 30
       elixirc_paths: elixirc_paths(Mix.env()),
31
-      elixirc_options: [warnings_as_errors: true],
32 31
       compilers: [:phoenix, :gettext, :phoenix_swagger] ++ Mix.compilers(),
33 32
       start_permanent: Mix.env() == :prod,
34 33
       aliases: aliases(),
@@ -155,6 +154,7 @@ defmodule Koype.Mixfile do
155 154
       {:social_parser, "~> 2.0.0"},
156 155
       {:sqlite_ecto2, "~> 2.2.5"},
157 156
       {:sweet_xml, "~> 0.6.6"},
157
+      {:tapex, "~> 0.1.0", only: :test},
158 158
       {:totpex, "~> 0.1.2"},
159 159
       {:uuid, "~> 1.1"},
160 160
       {:xml_builder, "~> 2.0.0", override: true}

+ 1
- 0
mix.lock View File

@@ -120,6 +120,7 @@
120 120
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
121 121
   "stream_gzip": {:hex, :stream_gzip, "0.3.1", "367ef2b91920cd6b906eeda78287a4da5330edf6e4147bbe1fc314802e0208bc", [:mix], [], "hexpm"},
122 122
   "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
123
+  "tapex": {:hex, :tapex, "0.1.1", "5ad627ae25323d3822373f04222fc14e0b13303ba2c83294c32b2a51a41f94ad", [:mix], [], "hexpm"},
123 124
   "tesla": {:git, "https://github.com/jalcine/tesla", "390a267a84d3b535f12e5efb6f58d66f157de57b", [branch: "jalcine/check-regex-run-results"]},
124 125
   "tesla_request_id": {:hex, :tesla_request_id, "0.2.0", "9745364ed3c850864fb2744daefb9b7104fe7f1efcf34eb350c61da805de2a46", [:mix], [{:tesla, "~> 1.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
125 126
   "timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [: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", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},

+ 16
- 0
priv/repo/migrations/20190420193439_create_page_parsers.exs View File

@@ -0,0 +1,16 @@
1
+defmodule Koype.Repo.Migrations.CreatePageParsers do
2
+  use Ecto.Migration
3
+
4
+  def change do
5
+    create table(:page_parsers) do
6
+      add(:url, :string, null: false)
7
+      add(:name, :string, null: false)
8
+      add(:deleted_at, :naive_datetime, default: nil)
9
+      add(:enabled, :boolean, default: false, null: false)
10
+      add(:method, :string, default: "get", null: false)
11
+      add(:extraction_path, :string)
12
+
13
+      timestamps()
14
+    end
15
+  end
16
+end

+ 7
- 0
priv/repo/migrations/20190420201029_make_url_of_parser_unique.exs View File

@@ -0,0 +1,7 @@
1
+defmodule Koype.Repo.Migrations.MakeUrlOfParserUnique do
2
+  use Ecto.Migration
3
+
4
+  def change do
5
+    create(unique_index(:page_parsers, [:url]))
6
+  end
7
+end

+ 44
- 0
priv/repo/migrations/20190420201821_add_preloaded_syndication_targets.exs View File

@@ -0,0 +1,44 @@
1
+defmodule Koype.Repo.Migrations.AddPreloadedSyndicationTargets do
2
+  use Ecto.Migration
3
+  require Ecto.Query
4
+
5
+  @preloaded_targets [
6
+    %{
7
+      name: "Twitter (brid.gy)",
8
+      endpoint: "https://brid.gy/publish/twitter"
9
+    },
10
+    %{
11
+      name: "GitHub (brid.gy)",
12
+      endpoint: "https://brid.gy/publish/github"
13
+    },
14
+    %{
15
+      name: "IndieWeb News",
16
+      endpoint: "https://news.indieweb.org/en"
17
+    }
18
+  ]
19
+
20
+  def up do
21
+    Enum.each(@preloaded_targets, fn target ->
22
+      endpoint = target[:endpoint]
23
+
24
+      if Koype.Repo.Syndication.Target
25
+         |> Ecto.Query.where(endpoint: ^endpoint)
26
+         |> Koype.Repo.one() == nil do
27
+        {:ok, _} = Koype.Repo.Syndication.Target.create(target)
28
+      end
29
+    end)
30
+  end
31
+
32
+  def down do
33
+    Enum.each(@preloaded_targets, fn target ->
34
+      endpoint = target[:endpoint]
35
+
36
+      case Koype.Repo.Syndication.Target
37
+           |> Ecto.Query.where(endpoint: ^endpoint)
38
+           |> Koype.Repo.one() do
39
+        nil -> :ok
40
+        target -> Koype.Repo.Syndication.Target.delete(target)
41
+      end
42
+    end)
43
+  end
44
+end

+ 3
- 1
priv/themes/default/tmpl/entry/view.html.liquid View File

@@ -1,6 +1,8 @@
1 1
 <article data-post-id="{{ entry.id }}"
2 2
          data-post-type="{{ entry.type }}"
3
-         class="mw8 w-100 ma0 pa2 flex flex-grow flex-wrap flex-auto flex-column flex-row-l content-stretch justify-center justify-between-l items-center items-start-l self-center {{ entry.class_type }}">
3
+         class="mw8 w-100 ma0 pa2 flex flex-grow flex-wrap flex-auto flex-column
4
+         flex-row-l content-stretch justify-center justify-between-l
5
+         items-center items-start-l self-center {{ entry.h_type }}">
4 6
   <main class="order-1 w-100 self-start items-stretch content-stretch flex flex-wrap flex-auto flex-grow">
5 7
     {% case entry.type %}
6 8
     {% when 'note' %}{% render name=entry/type/note.html %}

+ 20
- 26
priv/themes/default/tmpl/entry/view/byline.html.liquid View File

@@ -3,38 +3,32 @@
3 3
     published <time title="{{ entry.published_at }}" datetime="{{ entry.published_at }}" class="fw5 underline dt-published">{{ entry.published_at }}</time>
4 4
   </a>
5 5
   {% if entry.updated_at %}
6
-    <span class="v-mid db pt2-l">
6
+  <span class="v-mid db pt2-l">
7 7
       updated <time title="{{ entry.updated_at }}" datetime="{{ entry.updated_at }}" class="fw5 underline dt-updated">{{ entry.updated_at }}</time>
8
-    </span>
8
+  </span>
9 9
   {% endif %}
10 10
   <span class="v-mid h2 db pt2-l p-author h-card">
11 11
     by
12 12
     <img src="{{ h.card.photo }}" class="v-mid u-photo br-100 bw1 ba b--silver bg-silver w-auto h2" />
13 13
     <a class="v-mid dib color-inherit link u-url" href="{{ h.card.uri }}">
14 14
       <span class="p-name v-mid">{{ h.card.preferred_name }}</span>
15
-    </a>
15
+  </a>
16 16
   </span>
17
-  {% for result in entry.syndication %}
18
-    {% if forloop.first %}
19
-      <span class="v-mid db pt2-l">
20
-        <strong>Syndicated To:</strong>
21
-      {% if result.status == "completed" && result.uri != "" %}
22
-        <a title="{{ result.name }}"
23
-           data-syndication-target="{{ result.target }}"
24
-           class="u-syndication navy underline"
25
-           href="{{ result.uri }}">{{ result.name }}</a>
26
-      {% else %}
27
-        <a title="{{ result.name }}"
28
-           href="{{ result.endpoint }}"
29
-           class="u-syndication dim underline">{{ result.name }}</a>
30
-      {% endif %}
31
-    {% endfor %}
32
-    {% for result in entry.json.syndication %}
33
-      <a title="{{ result }}"
34
-         href="{{ result }}"
35
-         class="u-syndication"><i class="w1 h1 v-mid" data-feather="link"></i></a>
36
-      {% if forloop.last %}
37
-        </span>
38
-      {% endif %}
39
-    {% endfor %}
17
+  {% for result in entry.syndication_results %}
18
+    {% if forloop.first %}<span class="v-mid db pt2-l"><strong>Syndicated To:</strong>{% endif %}
19
+    {% if result.status == "completed" and result.url != "" %}
20
+      <a title="{{ result.target.name }}"
21
+          data-syndication-target="{{ result.target.id }}"
22
+          href="{{ result.url }}"
23
+          class="u-syndication navy underline"
24
+          >{{ result.target.name }}</a>
25
+    {% else %}
26
+      <a title="{{ result.target.name }}"
27
+          data-syndication-target="{{ result.target.id }}"
28
+          class="u-syndication dim underline"
29
+          href="{{ result.target.endpoint }}"
30
+          >{{ result.target.name }}</a>
31
+    {% endif %}
32
+    {% if forloop.last %}</span>{% endif %}
33
+  {% endfor %}
40 34
 </aside>

+ 3
- 3
priv/themes/default/tmpl/entry/view/generator.html.liquid View File

@@ -1,8 +1,8 @@
1 1
 {% assign app = entry.json["x-generator"] %}
2
-{% if app != nil %}
3
-  <p class="order-4 v-mid w-100 f6 lh-copy p-generated-with h-app h-x-app black-70">
2
+{% if app != nil and app["properties"] != nil %}
3
+  <p class="order-4 self-end v-mid w-100 f6 lh-copy p-generated-with h-app h-x-app black-70">
4 4
     Published using
5
-    {% if app["properties"]["logo"] != "" %}<img class="h2 br-100 w-auto v-mid u-logo ma1" src="{{ app["properties"]["logo"] }}" />{% endif %}
5
+    {% if app["properties"]["logo"] != nil %}<img class="h2 br-100 w-auto v-mid u-logo ma1" src="{{ app["properties"]["logo"] }}" />{% endif %}
6 6
     <a href="{{ app.properties.url }}" target="_blank" rel="nofollow" class="u-url link blue p-name">{{ app["properties"]["name"] }}</a>.
7 7
   </p>
8 8
 {% endif %}

+ 3
- 2
test/test_helper.exs View File

@@ -12,10 +12,11 @@ Bureaucrat.start(
12 12
   json_library: Jason
13 13
 )
14 14
 
15
+Ecto.Adapters.SQL.Sandbox.mode(Koype.Repo, :manual)
16
+
15 17
 ExUnit.configure(
16 18
   exclude: [slow: true, skip: true],
17
-  formatters: [ExUnit.CLIFormatter, Bureaucrat.Formatter]
19
+  formatters: [Tapex, Bureaucrat.Formatter]
18 20
 )
19 21
 
20
-Ecto.Adapters.SQL.Sandbox.mode(Koype.Repo, :manual)
21 22
 ExUnit.start()

+ 0
- 1
test/unit/storage_test.exs View File

@@ -62,7 +62,6 @@ defmodule Koype.StorageTest do
62 62
     test "successfully destroys a single image" do
63 63
       entry = insert(:entry)
64 64
       {:ok, file_name} = Koype.Storage.Image.store({@image_url, entry})
65
-      Apex.ap(file_name)
66 65
 
67 66
       assert :ok = Subject.destroy_property_for_record({"photo", file_name}, entry)
68 67
     end

+ 109
- 0
test/unit/template/format_test.exs View File

@@ -0,0 +1,109 @@
1
+defmodule Koype.Template.FormatterTest do
2
+  use Koype.Test.BaseCase, async: false
3
+  use Koype.DataCase
4
+  alias Koype.Template.Formatter, as: Subject
5
+  import Koype.Factory
6
+
7
+  describe ".format/1" do
8
+    test "DateTime: generats a RFC3339 string" do
9
+      dt = Faker.DateTime.forward(1)
10
+      assert Calendar.DateTime.Format.rfc3339(dt, 5) == Subject.format(dt)
11
+    end
12
+  end
13
+
14
+  describe ".format/1 = Koype.Repo.Category" do
15
+    test "generates JSON rendering of category" do
16
+      category = insert(:category)
17
+      category_json = Subject.format(category)
18
+      assert category_json["id"] == category.id
19
+      assert category_json["name"] == category.name
20
+      assert category_json["slug"] == category.slug
21
+      assert category_json["url"] == Koype.Repo.Category.get_uri(category)
22
+    end
23
+  end
24
+
25
+  describe ".format/1 = Koype.Repo.Syndication.Target" do
26
+    test "generates JSON rendering of a target for syndication" do
27
+      target = insert(:syndication_target)
28
+      target_json = Subject.format(target)
29
+
30
+      assert %{
31
+               "id" => target.id,
32
+               "endpoint" => target.endpoint,
33
+               "name" => target.name
34
+             } == target_json
35
+    end
36
+  end
37
+
38
+  describe ".format/1 = Koype.Repo.Entry.SyndicationTarget" do
39
+    test "generates JSON rendering of entry's syndication result" do
40
+      entry = insert(:entry)
41
+      target = insert(:syndication_target)
42
+
43
+      {:ok, result} =
44
+        Koype.Repo.Entry.SyndicationResult.create(entry, target, %{
45
+          status: "completed",
46
+          result: Faker.Internet.url()
47
+        })
48
+
49
+      entry_syndication_result = Koype.Repo.Entry.SyndicationResult.find(entry, target)
50
+
51
+      assert %{
52
+               "target" => %{
53
+                 "id" => target.id,
54
+                 "endpoint" => target.endpoint,
55
+                 "name" => target.name
56
+               },
57
+               "status" => result.status,
58
+               "url" => result.result
59
+             } == Subject.format(entry_syndication_result)
60
+    end
61
+  end
62
+
63
+  describe ".format/1 = Koype.Repo.Entry" do
64
+    test "generates JSON for entry" do
65
+      entry = insert(:entry)
66
+      entry_core_json = build(:entry_json)
67
+      categories = insert_list(3, :category)
68
+      :ok = Enum.each(categories, &Koype.Repo.Entry.Category.bind(entry, &1))
69
+      syndication_targets = insert_list(4, :syndication_target)
70
+      Koype.Repo.Entry.Json.persist(entry, entry_core_json)
71
+
72
+      :ok =
73
+        [syndication_targets, ~w(failed processed sent completed)]
74
+        |> Enum.zip()
75
+        |> Enum.each(fn {target, status} ->
76
+          {:ok, _result} =
77
+            Koype.Repo.Entry.SyndicationResult.create(entry, target, %{
78
+              status: status,
79
+              result: Faker.Internet.url()
80
+            })
81
+        end)
82
+
83
+      entry_syndication_results =
84
+        entry |> Koype.Repo.preload(:syndication_results) |> Map.get(:syndication_results)
85
+
86
+      syndication =
87
+        entry_syndication_results
88
+        |> Enum.map(&Subject.format/1)
89
+        |> Enum.uniq()
90
+
91
+      webmentions =
92
+        insert_list(3, :webmention,
93
+          target: entry |> Koype.Repo.Entry.get_uri() |> URI.parse() |> Map.get(:path)
94
+        )
95
+
96
+      entry_json = Subject.format(entry)
97
+      assert entry_json["id"] == entry.id
98
+      assert entry_json["title"] == entry.name
99
+      assert entry_json["json"] == entry_core_json
100
+      assert entry_json["h_type"] == "h-entry"
101
+      assert entry_json["interaction_count"] == Enum.count(webmentions)
102
+
103
+      assert entry_json["categories"] |> Enum.map(&Map.get(&1, "id")) |> Enum.sort() ==
104
+               categories |> Enum.map(&Map.get(&1, :id)) |> Enum.sort()
105
+
106
+      assert ^syndication = entry_json["syndication_results"]
107
+    end
108
+  end
109
+end

+ 0
- 1
web/controllers/entry_controller.ex View File

@@ -20,7 +20,6 @@ defmodule Koype.Web.EntryController do
20 20
   use Koype.Web, :controller
21 21
   alias Koype.Repo.Entry
22 22
   require Logger
23
-  plug(Logster.Plugs.ChangeLogLevel, to: :debug)
24 23
 
25 24
   def reparse(conn, %{"id" => id, "action" => "rename"}) do
26 25
     Logger.info("Beginning the renaming of Webmentions for #{id}...")

+ 36
- 9
web/controllers/settings_controller.ex View File

@@ -31,6 +31,16 @@ defmodule Koype.Web.SettingsController do
31 31
   def default_endpoint_for("indieauth.microsub.subscription"),
32 32
     do: IndieWeb.Microsub.inbuilt_subscription_endpoint()
33 33
 
34
+  def do_add_custom_parser(custom_parser)
35
+  def do_add_custom_parser(%{"url" => ""}), do: :ok
36
+
37
+  def do_add_custom_parser(custom_parser) do
38
+    case Koype.Repo.PageParser.create(custom_parser) do
39
+      {:ok, _parser} -> :ok
40
+      _ = error -> error
41
+    end
42
+  end
43
+
34 44
   def view(conn, %{"component" => component}) do
35 45
     case component do
36 46
       nil -> render(conn, "view.html", component: :hcard)
@@ -57,6 +67,7 @@ defmodule Koype.Web.SettingsController do
57 67
       end
58 68
 
59 69
       if params["photo"] do
70
+        Logger.info("Applying new profile photo...")
60 71
         Profile.set_photo(params["photo"])
61 72
       end
62 73
     end
@@ -177,15 +188,31 @@ defmodule Koype.Web.SettingsController do
177 188
     end
178 189
   end
179 190
 
180
-  def apply(conn, %{"component" => "url_parsing", "modules" => modules}) do
181
-    Enum.map(modules, &Koype.Page.Parser.enable(String.to_existing_atom(&1)))
191
+  def apply(conn, %{"component" => "url_parsing", "modules" => modules} = params) do
192
+    with(
193
+      :ok <- Enum.each(modules, &Koype.Page.Parser.enable(String.to_existing_atom(&1))),
194
+      :ok <-
195
+        Enum.each(Map.get(params, "custom_modules", []), &Koype.Page.Parser.enable(UUID.info!(&1))),
196
+      :ok <- do_add_custom_parser(Map.get(params, "custom_parser", %{}))
197
+    ) do
198
+      conn
199
+      |> put_flash(
200
+        :success,
201
+        "Updated URI parsing options."
202
+      )
203
+      |> put_status(:ok)
204
+      |> render("view.html", component: "url_parsing")
205
+    else
206
+      {:error, error} ->
207
+        Logger.warn("Failed to update parsing options for URLs.", error: inspect(error))
182 208
 
183
-    conn
184
-    |> put_flash(
185
-      :success,
186
-      "URI parsing list updated."
187
-    )
188
-    |> put_status(:ok)
189
-    |> render("view.html", component: "url_parsing")
209
+        conn
210
+        |> put_flash(
211
+          :error,
212
+          "Failed to update URL parsing options."
213
+        )
214
+        |> put_status(:bad_request)
215
+        |> render("view.html", component: "url_parsing")
216
+    end
190 217
   end
191 218
 end

+ 5
- 5
web/static/koype.scss View File

@@ -179,21 +179,21 @@ form div.entry {
179 179
   @extend .mv3, .justify-start, .items-end;
180 180
 
181 181
   > label {
182
-    @extend .measure, .measure-narrow-l, .self-start, .self-center-l;
182
+    @extend .measure, .measure-narrow-l, .self-start, .self-center-l, .w-100;
183 183
 
184 184
     > span {
185
-      @extend .b, .db, .f5, .lh-copy;
185
+      @extend .b, .db, .f5, .lh-copy, .black-80;
186 186
     }
187 187
 
188 188
     > small {
189
-      @extend .f6, .db, .measure, .measure-narrow-l, .lh-copy, .black-60;
189
+      @extend .f6, .db, .measure, .measure-narrow-l, .lh-copy, .black-60, .w-100;
190 190
     }
191 191
   }
192 192
 
193 193
   > input,
194 194
   > select,
195 195
   > textarea {
196
-    @extend .input-reset, .ba, .w-100, .b--black-20, .pa2, .mv2, .mv0-l;
196
+    @extend .input-reset, .ba, .w-100, .b--black-20, .pa2, .mv2, .mv0-l, .flex-auto;
197 197
 
198 198
     @extend .db, .self-start, .self-center-l, .measure, .justify-start, .ml4-l;
199 199
   }
@@ -220,5 +220,5 @@ body > header[role="banner"] {
220 220
 }
221 221
 
222 222
 mark {
223
-  @extend .yellow, .black-80;
223
+  @extend .bg-gold, .black-80, .pa1;
224 224
 }

+ 90
- 9
web/templates/settings/_view-url_parsing.html.eex View File

@@ -1,29 +1,34 @@
1 1
 <p class="lh-copy measure">
2 2
   Koype attempts to <em>translate</em> the pages it encounters on the Web into <a href="http://microformats.org" class="link">Microformats2</a>.
3
-  Below is a list of the in-built translation tools it uses.
4 3
 </p>
4
+<div class="flex flex-column flex-row-l items-start justify-start">
5
+<div class="w-third">
6
+<h3 class="f3 gray lh-title">Provided Solutions</h3>
5 7
 <dl>
6 8
   <dt class="lh-copy pv2">
7 9
   <div class="pretty p-success p-fill p-switch center">
8
-    <input type="checkbox" name="modules[]" value="as2" <%= if Koype.Page.Parser.enabled?(:as2), do: "checked" %> />
10
+    <input type="checkbox" name="modules[]" value="mf2"  <%= if Koype.Page.Parser.enabled?(:mf2), do: "checked" %> />
9 11
     <div class="state">
10
-      <label>ActivityStreams2 (Beta)</label>
12
+      <label>Microformats2</label>
11 13
     </div>
12 14
   </div>
13 15
   </dt>
14 16
   <dt class="lh-copy pv2">
15 17
   <div class="pretty p-success p-fill p-switch center">
16
-    <input type="checkbox" name="modules[]" value="mf2"  <%= if Koype.Page.Parser.enabled?(:mf2), do: "checked" %> />
18
+    <input type="checkbox" name="modules[]" value="bridgy"  <%= if Koype.Page.Parser.enabled?(:bridgy), do: "checked" %> />
17 19
     <div class="state">
18
-      <label>Microformats</label>
20
+      <label><a class="link" href="https://brid.gy">brid.gy</a> (Only Twitter)</label>
19 21
     </div>
20 22
   </div>
21 23
   </dt>
22 24
   <dt class="lh-copy pv2">
23 25
   <div class="pretty p-success p-fill p-switch center">
24
-    <input type="checkbox" name="modules[]" value="bridgy"  <%= if Koype.Page.Parser.enabled?(:bridgy), do: "checked" %> />
26
+    <input type="checkbox" name="modules[]" value="as2" <%= if Koype.Page.Parser.enabled?(:as2), do: "checked" %> />
25 27
     <div class="state">
26
-      <label><a class="link" href="https://brid.gy">brid.gy</a> (Only Twitter)</label>
28
+      <label>
29
+        <mark>Alpha</mark>
30
+        ActivityStreams2
31
+      </label>
27 32
     </div>
28 33
   </div>
29 34
   </dt>
@@ -31,7 +36,10 @@
31 36
   <div class="pretty p-success p-fill p-switch center">
32 37
     <input type="checkbox" name="modules[]" value="ogp"  <%= if Koype.Page.Parser.enabled?(:ogp), do: "checked" %> />
33 38
     <div class="state">
34
-      <label>Open Graph (Beta)</label>
39
+      <label>
40
+        <mark>Alpha</mark>
41
+        Open Graph (Beta)
42
+      </label>
35 43
     </div>
36 44
   </div>
37 45
   </dt>
@@ -39,8 +47,81 @@
39 47
   <div class="pretty p-success p-fill p-switch center">
40 48
     <input type="checkbox" name="modules[]" value="twtr"  <%= if Koype.Page.Parser.enabled?(:twtr), do: "checked" %> />
41 49
     <div class="state">
42
-      <label>Twitter Card Data (Beta)</label>
50
+      <label>
51
+        <mark>Alpha</mark>
52
+        Twitter Card Data (Beta)
53
+      </label>
43 54
     </div>
44 55
   </div>
45 56
   </dt>
46 57
 </dl>
58
+<h3 class="f3 gray lh-title">Custom Solutions</h3>
59
+<% parsers = Koype.Page.Parser.custom() %>
60
+<%= if Enum.empty?(parsers) do %>
61
+  <p class="lh-copy f5">No custom solutions set up. Use the form to the right to add some.</p>
62
+<% else %>
63
+<dl>
64
+  <%= for parser <- parsers do %>
65
+    <dt class="lh-copy pv2">
66
+    <div class="pretty p-success p-fill p-switch center">
67
+      <input type="checkbox" name="custom_modules[]" value="<%= parser.id %>" <%= if Koype.Page.Parser.enabled?(parser.id), do: "checked" %> />
68
+      <div class="state"><label><%= parser.name %></label></div>
69
+    </div>
70
+  <% end %>
71
+</dl>
72
+<% end %>
73
+</div>
74
+<div class="flex-auto flex-grow">
75
+<h3 class="f3 gray lh-title">Add A Custom Parser</h3>
76
+<fieldset class="bw0">
77
+  <div class="entry">
78
+    <label for="custom-parser-name">
79
+      <span>Name of Custom Parser</span>
80
+      <small id="custom-parser-name-desc">A friendler name to refer to this parser.</small>
81
+    </label>
82
+    <input name="custom_parser[name]" type="text" id="custom-parser-name" aria-describedby="custom-parser-name-desc" />
83
+  </div>
84
+  <div class="entry">
85
+    <label for="custom-parser-url">
86
+      <span>Parser URL</span>
87
+      <small id="custom-parser-url-desc">The URL to request when attempting to parse this response. </small>
88
+    </label>
89
+    <input class="code" name="custom_parser[url]" type="url" id="custom-parser-url" aria-describedby="custom-parser-url-desc" />
90
+  </div>
91
+  <div class="entry">
92
+    <label for="custom-parser-method">
93
+      <span>Request Method</span>
94
+      <small id="custom-parser-method-desc">The HTTP request verb to use when calling this endpoint.</small>
95
+    </label>
96
+    <select class="code" name="custom_parser[method]"id="custom-parser-method" aria-describedby="custom-parser-method-desc">
97
+      <option class="code" selected value="get">GET</option>
98
+      <option class="code" value="post">POST</option>
99
+    </select>
100
+  </div>
101
+  <div class="entry">
102
+    <label for="custom-parser-response-format">
103
+      <span>Response Format</span>
104
+      <small id="custom-parser-response-format-desc">The way that the provided response will be presented to Koype.</small>
105
+    </label>
106
+    <select name="custom_parser[response_format]"id="custom-parser-response-format" aria-describedby="custom-parser-response-format-desc">
107
+      <option class="code" selected value="mf2+json">Microformats2 as JSON</option>
108
+      <option class="code" value="jf2">JF2 (JSON Microformats2)</option>
109
+      <option class="code" value="mf2">Microformats2 + HTML</option>
110
+    </select>
111
+  </div>
112
+  <div class="entry">
113
+    <label for="custom-parser-extraction-path">
114
+      <span>Extraction Path</span>
115
+      <small id="custom-parser-extraction-path-desc">The JSONPath to extract the data from. If you're not sure; it's okay to leave this empty.</small>
116
+    </label>
117
+    <input class="code" name="custom_parser[extraction_path]" type="text" id="custom-parser-extraction-path" aria-describedby="custom-parser-extraction-path-desc" />
118
+  </div>
119
+  <div class="entry">
120
+    <p class="lh-copy measure f5">
121
+      If you're making a POST request, include the additional parameters in the URL field as part of the query parameters.
122
+      Include <code>${url}</code> to replace the URL to be parsed.
123
+    </p>
124
+  </div>
125
+</fieldset>
126
+</div>
127
+</div>

+ 6
- 13
web/views/entry_view.ex View File

@@ -23,19 +23,12 @@ defmodule Koype.Web.EntryView do
23 23
   @twitter_props ~w(twitter:card twitter:site twitter:creator twitter:title twitter:image:src twitter:description)a
24 24
 
25 25
   defp do_get_photo_uri(entry) do
26
-    photo_uris =
27
-      Map.get(entry["json"], "photo", [
28
-        %{"uri" => %{"original" => Koype.Profile.photo_uri()}}
29
-      ])
30
-
31
-    if is_binary(photo_uris) do
32
-      photo_uris
33
-    else
34
-      photo_uris
35
-      |> List.first()
36
-      |> Map.get("uri")
37
-      |> Map.get("original")
38
-    end
26
+    entry
27
+    |> Map.get("json")
28
+    |> Map.get("photo")
29
+    |> List.first()
30
+    |> Map.get("uri")
31
+    |> Map.get("original")
39 32
   end
40 33
 
41 34
   def title("entry/view", %{"entry" => entry}), do: entry["title"]

Loading…
Cancel
Save