Multiple sources structural changes

This commit is contained in:
byte[] 2021-10-08 21:33:16 -04:00 committed by Luna D
parent 1c5b07086e
commit 9bce2ca0a4
No known key found for this signature in database
GPG key ID: 4B1C63448394F688
12 changed files with 393 additions and 101 deletions

View file

@ -92,11 +92,6 @@ defmodule Philomena.Images do
|> Image.cache_changeset()
|> repo.update()
end)
|> Multi.run(:source_change, fn repo, %{image: image} ->
%SourceChange{image_id: image.id, initial: true}
|> SourceChange.creation_changeset(attrs, attribution)
|> repo.insert()
end)
|> Multi.run(:added_tag_count, fn repo, %{image: image} ->
tag_ids = image.added_tags |> Enum.map(& &1.id)
tags = Tag |> where([t], t.id in ^tag_ids)
@ -333,29 +328,69 @@ defmodule Philomena.Images do
|> Repo.update()
end
def update_source(%Image{} = image, attribution, attrs) do
image_changes =
image
|> Image.source_changeset(attrs)
source_changes =
Ecto.build_assoc(image, :source_changes)
|> SourceChange.creation_changeset(attrs, attribution)
def update_sources(%Image{} = image, attribution, attrs) do
old_sources = attrs["old_source_input"]
new_sources = attrs["source_input"]
Multi.new()
|> Multi.update(:image, image_changes)
|> Multi.run(:source_change, fn repo, _changes ->
case image_changes.changes do
%{source_url: _new_source} ->
repo.insert(source_changes)
|> Multi.run(:image, fn repo, _chg ->
image = repo.preload(image, [:sources])
_ ->
{:ok, nil}
image
|> Image.source_changeset(%{}, old_sources, new_sources)
|> repo.update()
|> case do
{:ok, image} ->
{:ok, {image, image.added_sources, image.removed_sources}}
error ->
error
end
end)
|> Multi.run(:added_source_changes, fn repo, %{image: {image, added_sources, _removed}} ->
source_changes =
added_sources
|> Enum.map(&source_change_attributes(attribution, image, &1, true, attribution[:user]))
{count, nil} = repo.insert_all(SourceChange, source_changes)
{:ok, count}
end)
|> Multi.run(:removed_source_changes, fn repo, %{image: {image, _added, removed_sources}} ->
source_changes =
removed_sources
|> Enum.map(&source_change_attributes(attribution, image, &1, false, attribution[:user]))
{count, nil} = repo.insert_all(SourceChange, source_changes)
{:ok, count}
end)
|> Repo.transaction()
end
defp source_change_attributes(attribution, image, source, added, user) do
now = DateTime.utc_now() |> DateTime.truncate(:second)
user_id =
case user do
nil -> nil
user -> user.id
end
%{
image_id: image.id,
source: source,
user_id: user_id,
created_at: now,
updated_at: now,
ip: attribution[:ip],
fingerprint: attribution[:fingerprint],
user_agent: attribution[:user_agent],
referrer: attribution[:referrer],
added: added
}
end
def update_locked_tags(%Image{} = image, attrs) do
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
@ -787,6 +822,7 @@ defmodule Philomena.Images do
:hiders,
:deleter,
:gallery_interactions,
:sources,
tags: [:aliases, :aliased_tag]
]
end

View file

@ -119,7 +119,7 @@ defmodule Philomena.Images.ElasticsearchIndex do
mime_type: image.image_mime_type,
uploader: if(!!image.user and !image.anonymous, do: String.downcase(image.user.name)),
true_uploader: if(!!image.user, do: String.downcase(image.user.name)),
source_url: image.source_url |> to_string |> String.downcase(),
source_url: image.sources |> Enum.map(&String.downcase(&1.source)),
file_name: image.image_name,
original_format: image.image_format,
processed: image.processed,

View file

@ -8,6 +8,7 @@ defmodule Philomena.Images.Image do
alias Philomena.ImageVotes.ImageVote
alias Philomena.ImageFaves.ImageFave
alias Philomena.ImageHides.ImageHide
alias Philomena.Images.Source
alias Philomena.Images.Subscription
alias Philomena.Users.User
alias Philomena.Tags.Tag
@ -18,6 +19,7 @@ defmodule Philomena.Images.Image do
alias Philomena.Images.Image
alias Philomena.Images.TagDiffer
alias Philomena.Images.SourceDiffer
alias Philomena.Images.TagValidator
alias Philomena.Images.DnpValidator
alias Philomena.Repo
@ -42,6 +44,7 @@ defmodule Philomena.Images.Image do
many_to_many :locked_tags, Tag, join_through: "image_tag_locks", on_replace: :delete
has_one :intensity, ImageIntensity
has_many :galleries, through: [:gallery_interactions, :image]
has_many :sources, Source
field :image, :string
field :image_name, :string
@ -91,6 +94,8 @@ defmodule Philomena.Images.Image do
field :removed_tags, {:array, :any}, default: [], virtual: true
field :added_tags, {:array, :any}, default: [], virtual: true
field :removed_sources, {:array, :any}, default: [], virtual: true
field :added_sources, {:array, :any}, default: [], virtual: true
field :uploaded_image, :string, virtual: true
field :removed_image, :string, virtual: true
@ -203,11 +208,10 @@ defmodule Philomena.Images.Image do
|> change(image: nil)
end
def source_changeset(image, attrs) do
def source_changeset(image, attrs, old_sources, new_sources) do
image
|> cast(attrs, [:source_url])
|> validate_required(:source_url)
|> validate_format(:source_url, ~r/\Ahttps?:\/\//)
|> cast(attrs, [])
|> SourceDiffer.diff_input(old_sources, new_sources)
end
def tag_changeset(image, attrs, old_tags, new_tags, excluded_tags \\ []) do

View file

@ -4,9 +4,10 @@ defmodule Philomena.Images.Source do
alias Philomena.Images.Image
@primary_key false
schema "image_sources" do
belongs_to :image, Image
field :source, :string
belongs_to :image, Image, primary_key: true
field :source, :string, primary_key: true
end
@doc false

View file

@ -0,0 +1,50 @@
defmodule Philomena.Images.SourceDiffer do
import Ecto.Changeset
alias Philomena.Images.Source
def diff_input(changeset, old_sources, new_sources) do
old_set = MapSet.new(old_sources)
new_set = MapSet.new(new_sources)
source_set = MapSet.new(get_field(changeset, :sources), & &1.source)
added_sources = MapSet.difference(new_set, old_set)
removed_sources = MapSet.difference(old_set, new_set)
{sources, actually_added, actually_removed} =
apply_changes(source_set, added_sources, removed_sources)
image_id = fetch_field!(changeset, :id)
changeset
|> put_change(:added_sources, actually_added)
|> put_change(:removed_sources, actually_removed)
|> put_assoc(:sources, source_structs(image_id, sources))
end
defp apply_changes(source_set, added_set, removed_set) do
desired_sources =
source_set
|> MapSet.difference(removed_set)
|> MapSet.union(added_set)
actually_added =
desired_sources
|> MapSet.difference(source_set)
|> Enum.to_list()
actually_removed =
source_set
|> MapSet.difference(desired_sources)
|> Enum.to_list()
sources = Enum.to_list(desired_sources)
actually_added = Enum.to_list(actually_added)
actually_removed = Enum.to_list(actually_removed)
{sources, actually_added, actually_removed}
end
defp source_structs(image_id, sources) do
Enum.map(sources, &%Source{image_id: image_id, source: &1})
end
end

View file

@ -10,10 +10,10 @@ defmodule Philomena.SourceChanges.SourceChange do
field :fingerprint, :string
field :user_agent, :string, default: ""
field :referrer, :string, default: ""
field :new_value, :string
field :initial, :boolean, default: false
field :value, :string
field :added, :boolean
field :source_url, :string, source: :new_value
field :source_url, :string, source: :value
timestamps(inserted_at: :created_at, type: :utc_datetime)
end

View file

@ -21,19 +21,18 @@ defmodule PhilomenaWeb.Image.SourceController do
plug :load_and_authorize_resource,
model: Image,
id_name: "image_id",
preload: [:user, tags: :aliases]
preload: [:user, :sources, tags: :aliases]
def update(conn, %{"image" => image_params}) do
attributes = conn.assigns.attributes
image = conn.assigns.image
old_source = image.source_url
case Images.update_source(image, attributes, image_params) do
{:ok, %{image: image}} ->
case Images.update_sources(image, attributes, image_params) do
{:ok, %{image: {image, added_sources, removed_sources}}} ->
PhilomenaWeb.Endpoint.broadcast!(
"firehose",
"image:source_update",
%{image_id: image.id, added: [image.source_url], removed: [old_source]}
%{image_id: image.id, added: [added_sources], removed: [removed_sources]}
)
PhilomenaWeb.Endpoint.broadcast!(
@ -49,7 +48,7 @@ defmodule PhilomenaWeb.Image.SourceController do
|> where(image_id: ^image.id)
|> Repo.aggregate(:count, :id)
if old_source != image.source_url do
if Enum.any?(added_sources) or Enum.any?(removed_sources) do
UserStatistics.inc_stat(conn.assigns.current_user, :metadata_updates)
end

View file

@ -185,7 +185,7 @@ defmodule PhilomenaWeb.ImageController do
_ in fragment("SELECT COUNT(*) FROM source_changes s WHERE s.image_id = ?", i.id),
on: true
)
|> preload([:deleter, :locked_tags, user: [awards: :badge], tags: :aliases])
|> preload([:deleter, :locked_tags, :sources, user: [awards: :badge], tags: :aliases])
|> select([i, t, s], {i, t.count, s.count})
|> Repo.one()
|> case do

View file

@ -25,15 +25,15 @@
' Source:
p
= if @image.source_url not in [nil, ""] do
a.js-source-link href=@image.source_url
strong
= @image.source_url
= if Enum.any?(@image.sources) do
= for source <- @image.sources do
a.js-source-link href=source.source
strong= source.source
- else
em> not provided yet
= if @source_change_count > 1 do
= if @source_change_count > 0 do
a.button.button--link.button--separate-left href=Routes.image_source_change_path(@conn, :index, @image) title="Source history"
i.fa.fa-history>
| History (

View file

@ -7,10 +7,10 @@
thead
tr
th colspan=2 Image
th New Source
th Source
th Action
th Timestamp
th User
th Initial?
tbody
= for source_change <- @source_changes do
@ -21,8 +21,13 @@
= render PhilomenaWeb.ImageView, "_image_container.html", image: source_change.image, size: :thumb_tiny, conn: @conn
td
= source_change.new_value
= source_change.source_url
= if source_change.added do
td.success Added
- else
td.danger Removed
td
= pretty_time(source_change.created_at)
@ -41,9 +46,5 @@
br
' Ask them before reverting their changes.
td
= if source_change.initial do
' &#x2713;
.block__header
= @pagination

View file

@ -0,0 +1,141 @@
defmodule Philomena.Repo.Migrations.RewriteSourceChanges do
use Ecto.Migration
def up do
rename table(:source_changes), to: table(:old_source_changes)
execute(
"alter index index_source_changes_on_image_id rename to index_old_source_changes_on_image_id"
)
execute(
"alter index index_source_changes_on_user_id rename to index_old_source_changes_on_user_id"
)
execute("alter index index_source_changes_on_ip rename to index_old_source_changes_on_ip")
execute(
"alter table old_source_changes rename constraint source_changes_pkey to old_source_changes_pkey"
)
execute("alter sequence source_changes_id_seq rename to old_source_changes_id_seq")
create table(:source_changes) do
add :image_id, references(:images, on_update: :update_all, on_delete: :delete_all),
null: false
add :user_id, references(:users, on_update: :update_all, on_delete: :delete_all)
add :ip, :inet, null: false
timestamps(inserted_at: :created_at)
add :added, :boolean, null: false
add :fingerprint, :string
add :user_agent, :string, default: ""
add :referrer, :string, default: ""
add :value, :string, null: false
end
alter table(:image_sources) do
remove :id
modify :source, :string
end
create index(:image_sources, [:image_id, :source],
name: "index_image_source_on_image_id_and_source",
unique: true
)
drop constraint(:image_sources, :length_must_be_valid,
check: "length(source) >= 8 and length(source) <= 1024"
)
create constraint(:image_sources, :image_sources_source_check,
check: "substr(source, 1, 7) = 'http://' or substr(source, 1, 8) = 'https://'"
)
execute("""
insert into image_sources (image_id, source)
select id as image_id, substr(source_url, 1, 255) as source from images
where source_url is not null and (substr(source_url, 1, 7) = 'http://' or substr(source_url, 1, 8) = 'https://');
""")
# First insert the "added" changes...
execute("""
with ranked_added_source_changes as (
select
image_id, user_id, ip, created_at, updated_at, fingerprint, user_agent,
substr(referrer, 1, 255) as referrer,
substr(new_value, 1, 255) as value, true as added,
rank() over (partition by image_id order by created_at asc)
from old_source_changes
where new_value is not null
)
insert into source_changes
(image_id, user_id, ip, created_at, updated_at, fingerprint, user_agent, referrer, value, added)
select image_id, user_id, ip, created_at, updated_at, fingerprint, user_agent, referrer, value, added
from ranked_added_source_changes
where "rank" > 1;
""")
# ...then the "removed" changes
execute("""
with ranked_removed_source_changes as (
select
image_id, user_id, ip, created_at, updated_at, fingerprint, user_agent,
substr(referrer, 1, 255) as referrer,
substr(new_value, 1, 255) as value, false as added,
rank() over (partition by image_id order by created_at desc)
from old_source_changes
where new_value is not null
)
insert into source_changes
(image_id, user_id, ip, created_at, updated_at, fingerprint, user_agent, referrer, value, added)
select image_id, user_id, ip, created_at, updated_at, fingerprint, user_agent, referrer, value, added
from ranked_removed_source_changes
where "rank" > 1;
""")
create index(:source_changes, [:image_id], name: "index_source_changes_on_image_id")
create index(:source_changes, [:user_id], name: "index_source_changes_on_user_id")
create index(:source_changes, [:ip], name: "index_source_changes_on_ip")
end
def down do
drop table(:source_changes)
rename table(:old_source_changes), to: table(:source_changes)
execute(
"alter index index_old_source_changes_on_image_id rename to index_source_changes_on_image_id"
)
execute(
"alter index index_old_source_changes_on_user_id rename to index_source_changes_on_user_id"
)
execute("alter index index_old_source_changes_on_ip rename to index_source_changes_on_ip")
execute(
"alter table source_changes rename constraint old_source_changes_pkey to source_changes_pkey"
)
execute("alter sequence old_source_changes_id_seq rename to source_changes_id_seq")
execute("truncate image_sources")
drop constraint(:image_sources, :image_sources_source_check,
check: "substr(source, 1, 7) = 'http://' or substr(source, 1, 8) = 'https://'"
)
create constraint(:image_sources, :length_must_be_valid,
check: "length(source) >= 8 and length(source) <= 1024"
)
drop index(:image_sources, [:image_id, :source],
name: "index_image_source_on_image_id_and_source"
)
alter table(:image_sources) do
modify :source, :text
end
end
end

View file

@ -842,32 +842,12 @@ ALTER SEQUENCE public.image_intensities_id_seq OWNED BY public.image_intensities
--
CREATE TABLE public.image_sources (
id bigint NOT NULL,
image_id bigint NOT NULL,
source text NOT NULL,
CONSTRAINT length_must_be_valid CHECK (((length(source) >= 8) AND (length(source) <= 1024)))
source character varying(255) NOT NULL,
CONSTRAINT image_sources_source_check CHECK (((substr((source)::text, 1, 7) = 'http://'::text) OR (substr((source)::text, 1, 8) = 'https://'::text)))
);
--
-- Name: image_sources_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.image_sources_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: image_sources_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.image_sources_id_seq OWNED BY public.image_sources.id;
--
-- Name: image_subscriptions; Type: TABLE; Schema: public; Owner: -
--
@ -1136,6 +1116,44 @@ CREATE SEQUENCE public.notifications_id_seq
ALTER SEQUENCE public.notifications_id_seq OWNED BY public.notifications.id;
--
-- Name: old_source_changes; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.old_source_changes (
id integer NOT NULL,
ip inet NOT NULL,
fingerprint character varying,
user_agent character varying DEFAULT ''::character varying,
referrer character varying DEFAULT ''::character varying,
new_value character varying,
initial boolean DEFAULT false NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
user_id integer,
image_id integer NOT NULL
);
--
-- Name: old_source_changes_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.old_source_changes_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: old_source_changes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.old_source_changes_id_seq OWNED BY public.old_source_changes.id;
--
-- Name: poll_options; Type: TABLE; Schema: public; Owner: -
--
@ -1414,17 +1432,17 @@ ALTER SEQUENCE public.site_notices_id_seq OWNED BY public.site_notices.id;
--
CREATE TABLE public.source_changes (
id integer NOT NULL,
id bigint NOT NULL,
image_id bigint NOT NULL,
user_id bigint,
ip inet NOT NULL,
fingerprint character varying,
user_agent character varying DEFAULT ''::character varying,
referrer character varying DEFAULT ''::character varying,
new_value character varying,
initial boolean DEFAULT false NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
user_id integer,
image_id integer NOT NULL
created_at timestamp(0) without time zone NOT NULL,
updated_at timestamp(0) without time zone NOT NULL,
added boolean NOT NULL,
fingerprint character varying(255),
user_agent character varying(255) DEFAULT ''::character varying,
referrer character varying(255) DEFAULT ''::character varying,
value character varying(255) NOT NULL
);
@ -2265,13 +2283,6 @@ ALTER TABLE ONLY public.image_features ALTER COLUMN id SET DEFAULT nextval('publ
ALTER TABLE ONLY public.image_intensities ALTER COLUMN id SET DEFAULT nextval('public.image_intensities_id_seq'::regclass);
--
-- Name: image_sources id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.image_sources ALTER COLUMN id SET DEFAULT nextval('public.image_sources_id_seq'::regclass);
--
-- Name: images id; Type: DEFAULT; Schema: public; Owner: -
--
@ -2307,6 +2318,13 @@ ALTER TABLE ONLY public.moderation_logs ALTER COLUMN id SET DEFAULT nextval('pub
ALTER TABLE ONLY public.notifications ALTER COLUMN id SET DEFAULT nextval('public.notifications_id_seq'::regclass);
--
-- Name: old_source_changes id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.old_source_changes ALTER COLUMN id SET DEFAULT nextval('public.old_source_changes_id_seq'::regclass);
--
-- Name: poll_options id; Type: DEFAULT; Schema: public; Owner: -
--
@ -2627,14 +2645,6 @@ ALTER TABLE ONLY public.image_intensities
ADD CONSTRAINT image_intensities_pkey PRIMARY KEY (id);
--
-- Name: image_sources image_sources_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.image_sources
ADD CONSTRAINT image_sources_pkey PRIMARY KEY (id);
--
-- Name: images images_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -2675,6 +2685,14 @@ ALTER TABLE ONLY public.notifications
ADD CONSTRAINT notifications_pkey PRIMARY KEY (id);
--
-- Name: old_source_changes old_source_changes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.old_source_changes
ADD CONSTRAINT old_source_changes_pkey PRIMARY KEY (id);
--
-- Name: poll_options poll_options_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -3407,6 +3425,13 @@ CREATE INDEX index_image_hides_on_user_id ON public.image_hides USING btree (use
CREATE UNIQUE INDEX index_image_intensities_on_image_id ON public.image_intensities USING btree (image_id);
--
-- Name: index_image_source_on_image_id_and_source; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX index_image_source_on_image_id_and_source ON public.image_sources USING btree (image_id, source);
--
-- Name: index_image_subscriptions_on_image_id_and_user_id; Type: INDEX; Schema: public; Owner: -
--
@ -3540,6 +3565,27 @@ CREATE INDEX index_mod_notes_on_notable_type_and_notable_id ON public.mod_notes
CREATE UNIQUE INDEX index_notifications_on_actor_id_and_actor_type ON public.notifications USING btree (actor_id, actor_type);
--
-- Name: index_old_source_changes_on_image_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_old_source_changes_on_image_id ON public.old_source_changes USING btree (image_id);
--
-- Name: index_old_source_changes_on_ip; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_old_source_changes_on_ip ON public.old_source_changes USING btree (ip);
--
-- Name: index_old_source_changes_on_user_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_old_source_changes_on_user_id ON public.old_source_changes USING btree (user_id);
--
-- Name: index_poll_options_on_poll_id_and_label; Type: INDEX; Schema: public; Owner: -
--
@ -4177,10 +4223,10 @@ ALTER TABLE ONLY public.image_taggings
--
-- Name: source_changes fk_rails_10271ec4d0; Type: FK CONSTRAINT; Schema: public; Owner: -
-- Name: old_source_changes fk_rails_10271ec4d0; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.source_changes
ALTER TABLE ONLY public.old_source_changes
ADD CONSTRAINT fk_rails_10271ec4d0 FOREIGN KEY (image_id) REFERENCES public.images(id) ON UPDATE CASCADE ON DELETE CASCADE;
@ -4577,10 +4623,10 @@ ALTER TABLE ONLY public.polls
--
-- Name: source_changes fk_rails_8d8cb9cb3b; Type: FK CONSTRAINT; Schema: public; Owner: -
-- Name: old_source_changes fk_rails_8d8cb9cb3b; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.source_changes
ALTER TABLE ONLY public.old_source_changes
ADD CONSTRAINT fk_rails_8d8cb9cb3b FOREIGN KEY (user_id) REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE SET NULL;
@ -4950,6 +4996,19 @@ ALTER TABLE ONLY public.image_tag_locks
ALTER TABLE ONLY public.moderation_logs
ADD CONSTRAINT moderation_logs_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
-- Name: source_changes source_changes_image_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.source_changes
ADD CONSTRAINT source_changes_image_id_fkey FOREIGN KEY (image_id) REFERENCES public.images(id) ON UPDATE CASCADE ON DELETE CASCADE;
--
-- Name: source_changes source_changes_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.source_changes
ADD CONSTRAINT source_changes_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE CASCADE;
--
@ -4988,6 +5047,7 @@ INSERT INTO public."schema_migrations" (version) VALUES (20210912171343);
INSERT INTO public."schema_migrations" (version) VALUES (20210917190346);
INSERT INTO public."schema_migrations" (version) VALUES (20210921025336);
INSERT INTO public."schema_migrations" (version) VALUES (20210929181319);
INSERT INTO public."schema_migrations" (version) VALUES (20211009011024);
INSERT INTO public."schema_migrations" (version) VALUES (20211107130226);
INSERT INTO public."schema_migrations" (version) VALUES (20211219194836);
INSERT INTO public."schema_migrations" (version) VALUES (20220321173359);