Switch to sqlite (#22)
* Updated project to use sqlite * Edited migrations directly because I fear no man * Updated search functions and tests to work with sqlite * Updated build tools to remove postgres
This commit is contained in:
parent
53bd8681c3
commit
b81c8d64b3
29 changed files with 121 additions and 186 deletions
|
|
@ -1,3 +0,0 @@
|
||||||
POSTGRES_HOST=postgres
|
|
||||||
POSTGRES_USER=postgres
|
|
||||||
POSTGRES_PASSWORD=postgres
|
|
||||||
3
.github/workflows/lint_and_test.yml
vendored
3
.github/workflows/lint_and_test.yml
vendored
|
|
@ -24,9 +24,6 @@ jobs:
|
||||||
- name: Create and populate ENV file
|
- name: Create and populate ENV file
|
||||||
run: |
|
run: |
|
||||||
echo MIX_ENV=test >> .env
|
echo MIX_ENV=test >> .env
|
||||||
echo POSTGRES_HOST=postgres >> .env
|
|
||||||
echo POSTGRES_USER=postgres >> .env
|
|
||||||
echo POSTGRES_PASSWORD=postgres >> .env
|
|
||||||
|
|
||||||
- name: Pull prebuilt images
|
- name: Pull prebuilt images
|
||||||
run: docker compose pull
|
run: docker compose pull
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -41,3 +41,7 @@ npm-debug.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
scratchpad.md
|
scratchpad.md
|
||||||
/scratchpad/
|
/scratchpad/
|
||||||
|
|
||||||
|
# Database files
|
||||||
|
*.db
|
||||||
|
*.db-*
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ FROM elixir:latest
|
||||||
|
|
||||||
# Install debian packages
|
# Install debian packages
|
||||||
RUN apt-get update -qq
|
RUN apt-get update -qq
|
||||||
RUN apt-get install -y inotify-tools postgresql-client ffmpeg \
|
RUN apt-get install -y inotify-tools ffmpeg \
|
||||||
python3 python3-pip python3-setuptools python3-wheel python3-dev
|
python3 python3-pip python3-setuptools python3-wheel python3-dev
|
||||||
|
|
||||||
# Install nodejs
|
# Install nodejs
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ config :pinchflat, PinchflatWeb.Endpoint,
|
||||||
live_view: [signing_salt: "/t5878kO"]
|
live_view: [signing_salt: "/t5878kO"]
|
||||||
|
|
||||||
config :pinchflat, Oban,
|
config :pinchflat, Oban,
|
||||||
|
engine: Oban.Engines.Lite,
|
||||||
repo: Pinchflat.Repo,
|
repo: Pinchflat.Repo,
|
||||||
# Keep old jobs for 30 days for display in the UI
|
# Keep old jobs for 30 days for display in the UI
|
||||||
plugins: [{Oban.Plugins.Pruner, max_age: 30 * 24 * 60 * 60}],
|
plugins: [{Oban.Plugins.Pruner, max_age: 30 * 24 * 60 * 60}],
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,9 @@ config :pinchflat,
|
||||||
|
|
||||||
# Configure your database
|
# Configure your database
|
||||||
config :pinchflat, Pinchflat.Repo,
|
config :pinchflat, Pinchflat.Repo,
|
||||||
username: System.get_env("POSTGRES_USER"),
|
database: Path.expand("../priv/repo/pinchflat_dev.db", Path.dirname(__ENV__.file)),
|
||||||
password: System.get_env("POSTGRES_PASSWORD"),
|
|
||||||
hostname: System.get_env("POSTGRES_HOST"),
|
|
||||||
database: "pinchflat_dev",
|
|
||||||
stacktrace: true,
|
|
||||||
show_sensitive_data_on_connection_error: true,
|
show_sensitive_data_on_connection_error: true,
|
||||||
pool_size: 10
|
pool_size: 5
|
||||||
|
|
||||||
# For development, we disable any cache and enable
|
# For development, we disable any cache and enable
|
||||||
# debugging and code reloading.
|
# debugging and code reloading.
|
||||||
|
|
|
||||||
|
|
@ -21,20 +21,16 @@ if System.get_env("PHX_SERVER") do
|
||||||
end
|
end
|
||||||
|
|
||||||
if config_env() == :prod do
|
if config_env() == :prod do
|
||||||
database_url =
|
database_path =
|
||||||
System.get_env("DATABASE_URL") ||
|
System.get_env("DATABASE_PATH") ||
|
||||||
raise """
|
raise """
|
||||||
environment variable DATABASE_URL is missing.
|
environment variable DATABASE_PATH is missing.
|
||||||
For example: ecto://USER:PASS@HOST/DATABASE
|
For example: /etc/pinchflat/pinchflat.db
|
||||||
"""
|
"""
|
||||||
|
|
||||||
maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
|
|
||||||
|
|
||||||
config :pinchflat, Pinchflat.Repo,
|
config :pinchflat, Pinchflat.Repo,
|
||||||
# ssl: true,
|
database: database_path,
|
||||||
url: database_url,
|
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "5")
|
||||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
|
||||||
socket_options: maybe_ipv6
|
|
||||||
|
|
||||||
# The secret key base is used to sign/encrypt cookies and other secrets.
|
# The secret key base is used to sign/encrypt cookies and other secrets.
|
||||||
# A default value is used in config/dev.exs and config/test.exs but you
|
# A default value is used in config/dev.exs and config/test.exs but you
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,9 @@ config :pinchflat, Oban, testing: :manual
|
||||||
# to provide built-in test partitioning in CI environment.
|
# to provide built-in test partitioning in CI environment.
|
||||||
# Run `mix help test` for more information.
|
# Run `mix help test` for more information.
|
||||||
config :pinchflat, Pinchflat.Repo,
|
config :pinchflat, Pinchflat.Repo,
|
||||||
username: System.get_env("POSTGRES_USER"),
|
database: Path.expand("../priv/repo/pinchflat_test.db", Path.dirname(__ENV__.file)),
|
||||||
password: System.get_env("POSTGRES_PASSWORD"),
|
pool_size: 5,
|
||||||
hostname: System.get_env("POSTGRES_HOST"),
|
pool: Ecto.Adapters.SQL.Sandbox
|
||||||
database: "pinchflat_test#{System.get_env("MIX_TEST_PARTITION")}",
|
|
||||||
pool: Ecto.Adapters.SQL.Sandbox,
|
|
||||||
pool_size: 10
|
|
||||||
|
|
||||||
# We don't run a server during test. If one is required,
|
# We don't run a server during test. If one is required,
|
||||||
# you can enable the server option below.
|
# you can enable the server option below.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
postgres:
|
|
||||||
image: 'postgres:16-alpine'
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
|
|
||||||
phx:
|
phx:
|
||||||
build: .
|
build: .
|
||||||
volumes:
|
volumes:
|
||||||
|
|
@ -13,4 +7,3 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- '4008:4008'
|
- '4008:4008'
|
||||||
command: tail -F /dev/null
|
command: tail -F /dev/null
|
||||||
env_file: .env
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,12 @@
|
||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
postgres:
|
|
||||||
image: 'postgres:16-alpine'
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
|
|
||||||
phx:
|
phx:
|
||||||
build: .
|
build: .
|
||||||
volumes:
|
volumes:
|
||||||
- '.:/app'
|
- '.:/app'
|
||||||
ports:
|
ports:
|
||||||
- '4008:4008'
|
- '4008:4008'
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
command:
|
command:
|
||||||
- ./docker-run.sh
|
- ./docker-run.sh
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,6 @@ echo "\nInstalling JS..."
|
||||||
cd assets && yarn install
|
cd assets && yarn install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# Wait for Postgres to become available.
|
|
||||||
export PGPASSWORD=$(echo $POSTGRES_PASSWORD)
|
|
||||||
until psql -h postgres -U $POSTGRES_USER -c '\q' 2>/dev/null; do
|
|
||||||
echo >&2 "Postgres is unavailable - sleeping"
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "\nPostgres is available: continuing with database setup..."
|
|
||||||
|
|
||||||
# Potentially Set up the database
|
# Potentially Set up the database
|
||||||
mix ecto.create
|
mix ecto.create
|
||||||
mix ecto.migrate
|
mix ecto.migrate
|
||||||
|
|
|
||||||
|
|
@ -51,31 +51,29 @@ defmodule Pinchflat.Media do
|
||||||
Returns a list of media_items that match the search term. Adds a `matching_search_term`
|
Returns a list of media_items that match the search term. Adds a `matching_search_term`
|
||||||
virtual field to the result set.
|
virtual field to the result set.
|
||||||
|
|
||||||
|
Has explit handling for blank search terms because SQLite doesn't like empty MATCH clauses.
|
||||||
|
|
||||||
Returns [%MediaItem{}, ...].
|
Returns [%MediaItem{}, ...].
|
||||||
"""
|
"""
|
||||||
def search(search_term, opts \\ []) do
|
def search(_search_term, _opts \\ [])
|
||||||
|
def search("", _opts), do: []
|
||||||
|
def search(nil, _opts), do: []
|
||||||
|
|
||||||
|
def search(search_term, opts) do
|
||||||
limit = Keyword.get(opts, :limit, 50)
|
limit = Keyword.get(opts, :limit, 50)
|
||||||
|
|
||||||
from(mi in MediaItem,
|
from(mi in MediaItem,
|
||||||
where: fragment("searchable @@ websearch_to_tsquery(?)", ^search_term),
|
join: mi_search_index in assoc(mi, :media_items_search_index),
|
||||||
|
where: fragment("media_items_search_index MATCH ?", ^search_term),
|
||||||
select_merge: %{
|
select_merge: %{
|
||||||
matching_search_term:
|
matching_search_term:
|
||||||
fragment(
|
fragment("""
|
||||||
"""
|
snippet(media_items_search_index, 0, '[PF_HIGHLIGHT]', '[/PF_HIGHLIGHT]', '...', 20) ||
|
||||||
ts_headline(
|
' ' ||
|
||||||
'english',
|
snippet(media_items_search_index, 1, '[PF_HIGHLIGHT]', '[/PF_HIGHLIGHT]', '...', 20)
|
||||||
CONCAT(title, ' ', description),
|
""")
|
||||||
websearch_to_tsquery(?),
|
|
||||||
'StartSel=[PF_HIGHLIGHT],StopSel=[/PF_HIGHLIGHT]'
|
|
||||||
)
|
|
||||||
""",
|
|
||||||
^search_term
|
|
||||||
)
|
|
||||||
},
|
|
||||||
order_by: {
|
|
||||||
:desc,
|
|
||||||
fragment("ts_rank_cd(searchable, websearch_to_tsquery(?), 0)", ^search_term)
|
|
||||||
},
|
},
|
||||||
|
order_by: [desc: fragment("rank")],
|
||||||
limit: ^limit
|
limit: ^limit
|
||||||
)
|
)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
@ -174,7 +172,10 @@ defmodule Pinchflat.Media do
|
||||||
Enum.reduce(mapped_struct, dynamic(true), fn attr, dynamic ->
|
Enum.reduce(mapped_struct, dynamic(true), fn attr, dynamic ->
|
||||||
case {attr, media_profile} do
|
case {attr, media_profile} do
|
||||||
{{:shorts_behaviour, :only}, %{livestream_behaviour: :only}} ->
|
{{:shorts_behaviour, :only}, %{livestream_behaviour: :only}} ->
|
||||||
dynamic([mi], ^dynamic and (mi.livestream == true or fragment("? ILIKE ?", mi.original_url, "%/shorts/%")))
|
dynamic(
|
||||||
|
[mi],
|
||||||
|
^dynamic and (mi.livestream == true or fragment("LOWER(?) LIKE LOWER(?)", mi.original_url, "%/shorts/%"))
|
||||||
|
)
|
||||||
|
|
||||||
# Technically redundant, but makes the other clauses easier to parse
|
# Technically redundant, but makes the other clauses easier to parse
|
||||||
# (redundant because this condition is the same as the condition above, just flipped)
|
# (redundant because this condition is the same as the condition above, just flipped)
|
||||||
|
|
@ -183,7 +184,7 @@ defmodule Pinchflat.Media do
|
||||||
|
|
||||||
{{:shorts_behaviour, :only}, _} ->
|
{{:shorts_behaviour, :only}, _} ->
|
||||||
# return records with /shorts/ in the original_url
|
# return records with /shorts/ in the original_url
|
||||||
dynamic([mi], ^dynamic and fragment("? ILIKE ?", mi.original_url, "%/shorts/%"))
|
dynamic([mi], ^dynamic and fragment("LOWER(?) LIKE LOWER(?)", mi.original_url, "%/shorts/%"))
|
||||||
|
|
||||||
{{:livestream_behaviour, :only}, _} ->
|
{{:livestream_behaviour, :only}, _} ->
|
||||||
# return records with livestream: true
|
# return records with livestream: true
|
||||||
|
|
@ -191,7 +192,7 @@ defmodule Pinchflat.Media do
|
||||||
|
|
||||||
{{:shorts_behaviour, :exclude}, %{livestream_behaviour: lb}} when lb != :only ->
|
{{:shorts_behaviour, :exclude}, %{livestream_behaviour: lb}} when lb != :only ->
|
||||||
# return records without /shorts/ in the original_url
|
# return records without /shorts/ in the original_url
|
||||||
dynamic([mi], ^dynamic and fragment("? NOT ILIKE ?", mi.original_url, "%/shorts/%"))
|
dynamic([mi], ^dynamic and fragment("LOWER(?) NOT LIKE LOWER(?)", mi.original_url, "%/shorts/%"))
|
||||||
|
|
||||||
{{:livestream_behaviour, :exclude}, %{shorts_behaviour: sb}} when sb != :only ->
|
{{:livestream_behaviour, :exclude}, %{shorts_behaviour: sb}} when sb != :only ->
|
||||||
# return records with livestream: false
|
# return records with livestream: false
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ defmodule Pinchflat.Media.MediaItem do
|
||||||
alias Pinchflat.Tasks.Task
|
alias Pinchflat.Tasks.Task
|
||||||
alias Pinchflat.MediaSource.Source
|
alias Pinchflat.MediaSource.Source
|
||||||
alias Pinchflat.Media.MediaMetadata
|
alias Pinchflat.Media.MediaMetadata
|
||||||
|
alias Pinchflat.Media.MediaItemSearchIndex
|
||||||
|
|
||||||
@allowed_fields ~w(
|
@allowed_fields ~w(
|
||||||
title
|
title
|
||||||
|
|
@ -46,6 +47,7 @@ defmodule Pinchflat.Media.MediaItem do
|
||||||
belongs_to :source, Source
|
belongs_to :source, Source
|
||||||
|
|
||||||
has_one :metadata, MediaMetadata, on_replace: :update
|
has_one :metadata, MediaMetadata, on_replace: :update
|
||||||
|
has_one :media_items_search_index, MediaItemSearchIndex, foreign_key: :id
|
||||||
|
|
||||||
has_many :tasks, Task
|
has_many :tasks, Task
|
||||||
|
|
||||||
|
|
|
||||||
16
lib/pinchflat/media/media_items_search_index.ex
Normal file
16
lib/pinchflat/media/media_items_search_index.ex
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Pinchflat.Media.MediaItemSearchIndex do
|
||||||
|
@moduledoc """
|
||||||
|
The MediaItem fts5 search index. Not made to be directly interacted with,
|
||||||
|
but I figured it'd be better to have it in-app so it's not a mystery.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
@primary_key {:id, :id, autogenerate: true, source: :rowid}
|
||||||
|
schema "media_items_search_index" do
|
||||||
|
field :title, :string
|
||||||
|
field :description, :string
|
||||||
|
|
||||||
|
field :rank, :float, virtual: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule Pinchflat.Repo do
|
defmodule Pinchflat.Repo do
|
||||||
use Ecto.Repo,
|
use Ecto.Repo,
|
||||||
otp_app: :pinchflat,
|
otp_app: :pinchflat,
|
||||||
adapter: Ecto.Adapters.Postgres
|
adapter: Ecto.Adapters.SQLite3
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
It's not immediately obvious if an Oban job qualifies as unique, so this method
|
It's not immediately obvious if an Oban job qualifies as unique, so this method
|
||||||
|
|
|
||||||
2
mix.exs
2
mix.exs
|
|
@ -35,7 +35,7 @@ defmodule Pinchflat.MixProject do
|
||||||
{:phoenix, "~> 1.7.10"},
|
{:phoenix, "~> 1.7.10"},
|
||||||
{:phoenix_ecto, "~> 4.4"},
|
{:phoenix_ecto, "~> 4.4"},
|
||||||
{:ecto_sql, "~> 3.10"},
|
{:ecto_sql, "~> 3.10"},
|
||||||
{:postgrex, ">= 0.0.0"},
|
{:ecto_sqlite3, ">= 0.0.0"},
|
||||||
{:phoenix_html, "~> 3.3"},
|
{:phoenix_html, "~> 3.3"},
|
||||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||||
{:phoenix_live_view, "~> 0.20.1"},
|
{:phoenix_live_view, "~> 0.20.1"},
|
||||||
|
|
|
||||||
4
mix.lock
4
mix.lock
|
|
@ -1,6 +1,7 @@
|
||||||
%{
|
%{
|
||||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||||
"castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
|
"castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
|
||||||
|
"cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"},
|
||||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
||||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||||
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
||||||
|
|
@ -10,8 +11,11 @@
|
||||||
"dns_cluster": {:hex, :dns_cluster, "0.1.2", "3eb5be824c7888dadf9781018e1a5f1d3d1113b333c50bce90fb1b83df1015f2", [:mix], [], "hexpm", "7494272040f847637bbdb01bcdf4b871e82daf09b813e7d3cb3b84f112c6f2f8"},
|
"dns_cluster": {:hex, :dns_cluster, "0.1.2", "3eb5be824c7888dadf9781018e1a5f1d3d1113b333c50bce90fb1b83df1015f2", [:mix], [], "hexpm", "7494272040f847637bbdb01bcdf4b871e82daf09b813e7d3cb3b84f112c6f2f8"},
|
||||||
"ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"},
|
"ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
|
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
|
||||||
|
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.15.1", "40f2fbd9e246455f8c42e7e0a77009ef806caa1b3ce6f717b2a0a80e8432fcfd", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.19", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "28b16e177123c688948357176662bf9ff9084daddf950ef5b6baf3ee93707064"},
|
||||||
|
"elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"},
|
||||||
"esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
|
"esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
|
||||||
"expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"},
|
"expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"},
|
||||||
|
"exqlite": {:hex, :exqlite, "0.19.0", "0f3ee29e35bed38552dd0ed59600aa81c78f867f5b5ff0e17d330148e0465483", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "55a8fbb0443f03d4a256e3458bd1203eff5037a6624b76460eaaa9080f462b06"},
|
||||||
"faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"},
|
"faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"},
|
||||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
||||||
"finch": {:hex, :finch, "0.17.0", "17d06e1d44d891d20dbd437335eebe844e2426a0cd7e3a3e220b461127c73f70", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8d014a661bb6a437263d4b5abf0bcbd3cf0deb26b1e8596f2a271d22e48934c7"},
|
"finch": {:hex, :finch, "0.17.0", "17d06e1d44d891d20dbd437335eebe844e2426a0cd7e3a3e220b461127c73f70", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8d014a661bb6a437263d4b5abf0bcbd3cf0deb26b1e8596f2a271d22e48934c7"},
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
defmodule Pinchflat.Repo.Migrations.CreateChannels do
|
defmodule Pinchflat.Repo.Migrations.CreateSources do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:channels) do
|
create table(:sources) do
|
||||||
add :name, :string, null: false
|
add :name, :string, null: false
|
||||||
add :channel_id, :string, null: false
|
add :collection_id, :string, null: false
|
||||||
|
add :collection_type, :string, null: false
|
||||||
add :original_url, :string, null: false
|
add :original_url, :string, null: false
|
||||||
add :media_profile_id, references(:media_profiles, on_delete: :restrict), null: false
|
add :media_profile_id, references(:media_profiles, on_delete: :restrict), null: false
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
create index(:channels, [:media_profile_id])
|
create index(:sources, [:media_profile_id])
|
||||||
create unique_index(:channels, [:channel_id, :media_profile_id])
|
create unique_index(:sources, [:collection_id, :media_profile_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ defmodule Pinchflat.Repo.Migrations.CreateMediaItems do
|
||||||
add :media_id, :string, null: false
|
add :media_id, :string, null: false
|
||||||
add :title, :string
|
add :title, :string
|
||||||
add :video_filepath, :string
|
add :video_filepath, :string
|
||||||
add :channel_id, references(:channels, on_delete: :restrict), null: false
|
add :source_id, references(:sources, on_delete: :restrict), null: false
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
create index(:media_items, [:channel_id])
|
create index(:media_items, [:source_id])
|
||||||
create unique_index(:media_items, [:media_id, :channel_id])
|
create unique_index(:media_items, [:media_id, :source_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
defmodule Pinchflat.Repo.Migrations.AddIndexFrequencyToChannels do
|
defmodule Pinchflat.Repo.Migrations.AddIndexFrequencyToSources do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
alter table(:channels) do
|
alter table(:sources) do
|
||||||
add :index_frequency_minutes, :integer, default: 60 * 24, null: false
|
add :index_frequency_minutes, :integer, default: 60 * 24, null: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ defmodule Pinchflat.Repo.Migrations.CreateTasks do
|
||||||
def change do
|
def change do
|
||||||
create table(:tasks) do
|
create table(:tasks) do
|
||||||
add :job_id, references(:oban_jobs, on_delete: :delete_all), null: false
|
add :job_id, references(:oban_jobs, on_delete: :delete_all), null: false
|
||||||
# `restrict` because we need to be sure to delete pending tasks when a channel is deleted
|
# `restrict` because we need to be sure to delete pending tasks when a source is deleted
|
||||||
add :channel_id, references(:channels, on_delete: :restrict), null: true
|
add :source_id, references(:sources, on_delete: :restrict), null: true
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
create index(:tasks, [:job_id])
|
create index(:tasks, [:job_id])
|
||||||
create index(:tasks, [:channel_id])
|
create index(:tasks, [:source_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ defmodule Pinchflat.Repo.Migrations.CreateMediaMetadata do
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create table(:media_metadata) do
|
create table(:media_metadata) do
|
||||||
add :client_response, :jsonb, null: false
|
add :client_response, :json, null: false
|
||||||
add :media_item_id, references(:media_items, on_delete: :delete_all), null: false
|
add :media_item_id, references(:media_items, on_delete: :delete_all), null: false
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
||||||
create unique_index(:media_metadata, [:media_item_id])
|
create unique_index(:media_metadata, [:media_item_id])
|
||||||
create index(:media_metadata, [:client_response], using: :gin)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ defmodule Pinchflat.Repo.Migrations.AddMediaItemToTasks do
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
alter table(:tasks) do
|
alter table(:tasks) do
|
||||||
# `restrict` because we need to be sure to delete pending tasks when a channel is deleted
|
# `restrict` because we need to be sure to delete pending tasks when a media item is deleted
|
||||||
add :media_item_id, references(:media_items, on_delete: :restrict), null: true
|
add :media_item_id, references(:media_items, on_delete: :restrict), null: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
defmodule Pinchflat.Repo.Migrations.RenameChannelAndRelatedFields do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def change do
|
|
||||||
# Creation
|
|
||||||
create table(:sources) do
|
|
||||||
add :name, :string, null: false
|
|
||||||
add :collection_type, :string, null: false
|
|
||||||
add :collection_id, :string, null: false
|
|
||||||
add :original_url, :string, null: false
|
|
||||||
add :media_profile_id, references(:media_profiles, on_delete: :restrict), null: false
|
|
||||||
add :index_frequency_minutes, :integer, default: 60 * 24, null: false
|
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
|
||||||
end
|
|
||||||
|
|
||||||
alter table(:media_items) do
|
|
||||||
add :source_id, references(:sources, on_delete: :restrict), null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
alter table(:tasks) do
|
|
||||||
# `restrict` because we need to be sure to delete pending tasks when a source is deleted
|
|
||||||
add :source_id, references(:sources, on_delete: :restrict), null: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create index(:sources, [:media_profile_id])
|
|
||||||
create unique_index(:sources, [:collection_id, :media_profile_id])
|
|
||||||
|
|
||||||
create index(:media_items, [:source_id])
|
|
||||||
create unique_index(:media_items, [:media_id, :source_id])
|
|
||||||
|
|
||||||
# Deletion
|
|
||||||
drop index(:media_items, [:channel_id])
|
|
||||||
drop unique_index(:media_items, [:media_id, :channel_id])
|
|
||||||
|
|
||||||
alter table(:tasks) do
|
|
||||||
# `restrict` because we need to be sure to delete pending tasks when a source is deleted
|
|
||||||
remove :channel_id, references(:channels, on_delete: :restrict), null: true
|
|
||||||
end
|
|
||||||
|
|
||||||
alter table(:media_items) do
|
|
||||||
remove :channel_id, references(:channels, on_delete: :restrict), null: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
defmodule Pinchflat.Repo.Migrations.DropChannelsTable do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def up do
|
|
||||||
drop table(:channels)
|
|
||||||
end
|
|
||||||
|
|
||||||
def down do
|
|
||||||
create table(:channels) do
|
|
||||||
add :name, :string, null: false
|
|
||||||
add :channel_id, :string, null: false
|
|
||||||
add :original_url, :string, null: false
|
|
||||||
add :media_profile_id, references(:media_profiles, on_delete: :restrict), null: false
|
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
|
||||||
end
|
|
||||||
|
|
||||||
create index(:channels, [:media_profile_id])
|
|
||||||
create unique_index(:channels, [:channel_id, :media_profile_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -2,27 +2,50 @@ defmodule Pinchflat.Repo.Migrations.AddSearchFieldToMediaItems do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
def up do
|
def up do
|
||||||
|
# These all need to run as part of separate `execute` blocks. Do NOT ask me why.
|
||||||
execute """
|
execute """
|
||||||
ALTER TABLE media_items
|
CREATE VIRTUAL TABLE media_items_search_index USING fts5(
|
||||||
ADD COLUMN searchable tsvector
|
title,
|
||||||
GENERATED ALWAYS AS (
|
description,
|
||||||
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
|
tokenize=porter
|
||||||
setweight(to_tsvector('english', coalesce(description, '')), 'B')
|
);
|
||||||
) STORED;
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
execute """
|
execute """
|
||||||
CREATE INDEX media_items_searchable_idx ON media_items USING gin(searchable);
|
CREATE TRIGGER media_items_search_index_insert AFTER INSERT ON media_items BEGIN
|
||||||
|
INSERT INTO media_items_search_index(
|
||||||
|
rowid,
|
||||||
|
title,
|
||||||
|
description
|
||||||
|
)
|
||||||
|
VALUES(
|
||||||
|
new.id,
|
||||||
|
new.title,
|
||||||
|
new.description
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
"""
|
||||||
|
|
||||||
|
execute """
|
||||||
|
CREATE TRIGGER media_items_search_index_update AFTER UPDATE ON media_items BEGIN
|
||||||
|
UPDATE media_items_search_index SET
|
||||||
|
title = new.title,
|
||||||
|
description = new.description
|
||||||
|
WHERE
|
||||||
|
rowid = old.id;
|
||||||
|
END;
|
||||||
|
"""
|
||||||
|
|
||||||
|
execute """
|
||||||
|
CREATE TRIGGER media_items_search_index_delete AFTER DELETE ON media_items BEGIN
|
||||||
|
DELETE FROM media_items_search_index WHERE rowid = old.id;
|
||||||
|
END;
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def down do
|
def down do
|
||||||
execute """
|
execute """
|
||||||
DROP INDEX media_items_searchable_idx;
|
DROP TABLE media_items_search_index;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alter table(:media_items) do
|
|
||||||
remove :searchable
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,14 @@ defmodule Pinchflat.MediaTest do
|
||||||
|
|
||||||
assert [_] = Media.search("dog", limit: 1)
|
assert [_] = Media.search("dog", limit: 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "returns an empty list when the search term is blank" do
|
||||||
|
assert [] = Media.search("")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns an empty list when the search term is nil" do
|
||||||
|
assert [] = Media.search(nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "get_media_item!/1" do
|
describe "get_media_item!/1" do
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ defmodule PinchflatWeb.ConnCase do
|
||||||
|
|
||||||
setup tags do
|
setup tags do
|
||||||
Pinchflat.DataCase.setup_sandbox(tags)
|
Pinchflat.DataCase.setup_sandbox(tags)
|
||||||
Pinchflat.DataCase.setup_temp_filepaths()
|
|
||||||
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,6 @@ defmodule Pinchflat.DataCase do
|
||||||
|
|
||||||
setup tags do
|
setup tags do
|
||||||
Pinchflat.DataCase.setup_sandbox(tags)
|
Pinchflat.DataCase.setup_sandbox(tags)
|
||||||
Pinchflat.DataCase.setup_temp_filepaths()
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Sets up the temp filepaths for the media and metadata directories.
|
|
||||||
"""
|
|
||||||
def setup_temp_filepaths do
|
|
||||||
File.rm_rf!(Application.get_env(:pinchflat, :media_directory))
|
|
||||||
File.rm_rf!(Application.get_env(:pinchflat, :metadata_directory))
|
|
||||||
|
|
||||||
File.mkdir_p!(Application.get_env(:pinchflat, :media_directory))
|
|
||||||
File.mkdir_p!(Application.get_env(:pinchflat, :metadata_directory))
|
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue