improvement: initial discord bot support, indexing messages:wq

This commit is contained in:
Zach Daniel 2023-01-22 01:36:48 -05:00
parent 406d13ddb2
commit fc6573bfdd
36 changed files with 1851 additions and 51 deletions

View file

@ -12,7 +12,8 @@ config :ash_hq,
config :ash, allow_flow: true
config :ash_hq, ash_apis: [AshHq.Blog, AshHq.Docs, AshHq.Accounts, AshHq.MailingList]
config :ash_hq,
ash_apis: [AshHq.Blog, AshHq.Docs, AshHq.Accounts, AshHq.MailingList, AshHq.Discord]
config :ash_hq, AshHq.Repo,
timeout: :timer.minutes(10),

View file

@ -26,6 +26,9 @@ if config_env() != :dev do
config :logger, level: String.to_existing_atom(System.get_env("LOG_LEVEL") || "info")
end
config :nostrum,
token: System.get_env("DISCORD_BOT_TOKEN")
if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||

View file

@ -7,7 +7,7 @@ kill_timeout = 5
processes = []
[deploy]
release_command = "_build/prod/rel/ash_hq/bin/ash_hq eval 'AshHq.Release.migrate'"
# release_command = "_build/prod/rel/ash_hq/bin/ash_hq eval 'AshHq.Release.migrate'"
[env]

View file

@ -29,7 +29,8 @@ defmodule AshHq.Application do
{Phoenix.PubSub, name: AshHq.PubSub},
# Start the Endpoint (http/https)
AshHqWeb.Endpoint,
{AshHq.Docs.Library.Agent, nil}
{AshHq.Docs.Library.Agent, nil},
AshHq.Discord.Supervisor
# Start a worker by calling: AshHq.Worker.start_link(arg)
# {AshHq.Worker, arg}

View file

@ -0,0 +1,7 @@
defmodule AshHq.Discord do
use Ash.Api
resources do
registry AshHq.Discord.Registry
end
end

View file

@ -0,0 +1,20 @@
defmodule AshHq.Discord.Listener do
use Nostrum.Consumer
# alias Nostrum.Api
def start_link do
Consumer.start_link(__MODULE__)
end
# Lets set up slash commands later
# def handle_event({:MESSAGE_CREATE, msg, _ws_state}) do
# IO.inspect(msg)
# end
# Default event handler, if you don't include this, your consumer WILL crash if
# you don't have a method definition for each event type.
def handle_event(_event) do
:noop
end
end

View file

@ -0,0 +1,166 @@
defmodule AshHq.Discord.Poller do
use GenServer
@poll_interval :timer.hours(2)
@server_id 711_271_361_523_351_632
@archived_thread_lookback 20
@channels [
1_066_222_835_758_014_606,
1_066_223_107_922_210_867,
1_019_647_368_196_534_283
]
def server_id, do: @server_id
defmacrop unwrap(value) do
quote do
case unquote(value) do
{:ok, value} ->
value
{:error, error} ->
raise Exception.format(:error, error, [])
end
end
end
def start_link(state) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
def init(_) do
Process.send_after(self(), :poll, @poll_interval)
{:ok, nil}
end
def handle_info(:poll, state) do
poll()
Process.send_after(self(), :poll, @poll_interval)
{:noreply, state}
end
def poll() do
for {channel, index} <- Enum.with_index(@channels) do
channel
|> Nostrum.Api.get_channel!()
|> tap(fn channel ->
channel
|> Map.from_struct()
|> Map.put(:order, index)
|> AshHq.Discord.Channel.upsert!()
end)
|> Map.get(:available_tags)
|> Enum.each(fn available_tag ->
AshHq.Discord.Tag.upsert!(channel, available_tag.id, available_tag.name)
end)
end
active =
@server_id
|> Nostrum.Api.list_guild_threads()
|> unwrap()
|> Map.get(:threads)
|> Stream.filter(fn thread ->
thread.parent_id in @channels
end)
|> Stream.map(fn thread ->
%{
thread: thread,
messages: get_all_channel_messages(thread.id)
}
end)
archived =
@channels
|> Stream.flat_map(fn channel ->
channel
|> Nostrum.Api.list_public_archived_threads(limit: @archived_thread_lookback)
|> unwrap()
|> Map.get(:threads)
|> Enum.map(fn thread ->
messages =
thread.id
|> get_all_channel_messages()
%{
thread: thread,
messages: messages
}
end)
end)
active
|> Stream.concat(archived)
|> Enum.reject(fn
%{messages: []} ->
true
_ ->
false
end)
|> Enum.map(fn %{thread: thread, messages: messages} ->
thread
|> Map.put(:author, Enum.at(messages, 0).author)
|> Map.from_struct()
|> Map.put(:channel_id, thread.parent_id)
|> Map.put(:tags, thread.applied_tags)
|> Map.put(:create_timestamp, thread.thread_metadata.create_timestamp)
|> Map.put(:messages, Enum.map(messages, &Map.from_struct/1))
|> AshHq.Discord.Thread.upsert!()
end)
end
defp get_all_channel_messages(thread) do
Stream.resource(
fn ->
:all
end,
fn
nil ->
{:halt, nil}
before ->
locator =
case before do
:all ->
nil
before ->
{:before, before}
end
messages =
if locator do
Nostrum.Api.get_channel_messages!(thread, 100, locator)
else
Nostrum.Api.get_channel_messages!(thread, 100)
end
if Enum.count(messages) == 100 do
{messages, List.last(messages).id}
else
{messages, nil}
end
end,
& &1
)
|> Stream.map(fn message ->
message
|> Map.put(:author, message.author.username)
|> Map.update!(:reactions, fn reactions ->
reactions
|> Kernel.||([])
# just don't know what this looks like, so removing them
|> Enum.reject(&(is_nil(&1.emoji) || &1.emoji == "" || &1.emoji.animated))
|> Enum.map(fn %{count: count, emoji: emoji} ->
%{emoji: emoji.name, count: count}
end)
end)
|> Map.update!(:attachments, fn attachments ->
Enum.map(attachments, &Map.from_struct/1)
end)
end)
|> Enum.to_list()
end
end

View file

@ -0,0 +1,14 @@
defmodule AshHq.Discord.Registry do
use Ash.Registry,
extensions: [Ash.Registry.ResourceValidations]
entries do
entry AshHq.Discord.Attachment
entry AshHq.Discord.Channel
entry AshHq.Discord.Message
entry AshHq.Discord.Reaction
entry AshHq.Discord.Tag
entry AshHq.Discord.Thread
entry AshHq.Discord.ThreadTag
end
end

View file

@ -0,0 +1,30 @@
defmodule AshHq.Discord.Attachment do
use Ash.Resource,
data_layer: AshPostgres.DataLayer
postgres do
table "discord_attachments"
repo AshHq.Repo
end
actions do
defaults [:create, :read, :update, :destroy]
end
attributes do
integer_primary_key :id, generated?: false, writable?: true
attribute :filename, :string
attribute :size, :integer
attribute :url, :string
attribute :proxy_url, :string
attribute :height, :integer
attribute :width, :integer
end
relationships do
belongs_to :message, AshHq.Discord.Message do
allow_nil? false
attribute_type :integer
end
end
end

View file

@ -0,0 +1,39 @@
defmodule AshHq.Discord.Channel do
use Ash.Resource,
data_layer: AshPostgres.DataLayer
postgres do
table "discord_channels"
repo AshHq.Repo
end
actions do
defaults [:create, :read, :update, :destroy]
create :upsert do
upsert? true
end
end
code_interface do
define_for AshHq.Discord
define :read
define :upsert
end
attributes do
integer_primary_key :id, writable?: true, generated?: false
attribute :name, :string do
allow_nil? false
end
attribute :order, :integer do
allow_nil? false
end
end
relationships do
has_many :threads, AshHq.Discord.Thread
end
end

View file

@ -0,0 +1,67 @@
defmodule AshHq.Discord.Message do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshHq.Docs.Extensions.RenderMarkdown]
postgres do
table "discord_messages"
repo AshHq.Repo
end
render_markdown do
render_attributes content: :content_html
end
actions do
defaults [:read, :destroy]
create :create do
primary? true
argument :attachments, {:array, :map}
argument :reactions, {:array, :map}
change manage_relationship(:attachments, type: :direct_control)
change manage_relationship(:reactions,
type: :direct_control,
use_identities: [:unique_message_emoji]
)
end
update :update do
primary? true
argument :attachments, {:array, :map}
argument :reactions, {:array, :map}
change manage_relationship(:attachments, type: :direct_control)
change manage_relationship(:reactions,
type: :direct_control,
use_identities: [:unique_message_emoji]
)
end
end
attributes do
integer_primary_key :id, generated?: false, writable?: true
attribute :author, :string do
allow_nil? false
end
attribute :content, :string
attribute :content_html, :string
attribute :timestamp, :utc_datetime do
allow_nil? false
end
end
relationships do
belongs_to :thread, AshHq.Discord.Thread do
attribute_type :integer
allow_nil? false
end
has_many :attachments, AshHq.Discord.Attachment
has_many :reactions, AshHq.Discord.Reaction
end
end

View file

@ -0,0 +1,36 @@
defmodule AshHq.Discord.Reaction do
use Ash.Resource,
data_layer: AshPostgres.DataLayer
postgres do
table "discord_reactions"
repo AshHq.Repo
end
actions do
defaults [:create, :read, :update, :destroy]
end
attributes do
uuid_primary_key :id
attribute :count, :integer do
allow_nil? false
end
attribute :emoji, :string do
allow_nil? false
end
end
identities do
identity :unique_message_emoji, [:emoji, :message_id]
end
relationships do
belongs_to :message, AshHq.Discord.Message do
attribute_type :integer
allow_nil? false
end
end
end

View file

@ -0,0 +1,45 @@
defmodule AshHq.Discord.Tag do
@moduledoc "A tag that can be applied to a post. Currently uses CSV data layer and therefore is static"
use Ash.Resource,
data_layer: AshPostgres.DataLayer
postgres do
table "discord_tags"
repo AshHq.Repo
end
attributes do
integer_primary_key :id, generated?: false, writable?: true
attribute :name, :ci_string do
allow_nil? false
end
end
actions do
defaults [:create, :read, :update, :destroy]
create :upsert do
upsert? true
upsert_identity :unique_name_per_channel
end
end
identities do
identity :unique_name_per_channel, [:name, :channel_id]
end
code_interface do
define_for AshHq.Discord
define :upsert, args: [:channel_id, :id, :name]
define :read
define :destroy
end
relationships do
belongs_to :channel, AshHq.Discord.Channel do
attribute_type :integer
attribute_writable? true
end
end
end

View file

@ -0,0 +1,105 @@
defmodule AshHq.Discord.Thread do
use Ash.Resource,
data_layer: AshPostgres.DataLayer
import Ecto.Query
postgres do
table "discord_threads"
repo AshHq.Repo
end
actions do
defaults [:create, :read, :update, :destroy]
read :feed do
pagination do
countable true
offset? true
default_limit 25
end
argument :channel, :integer do
allow_nil? false
end
argument :tag_name, :string
prepare build(sort: [create_timestamp: :desc])
filter expr(
channel_id == ^arg(:channel) and
(is_nil(^arg(:tag_name)) or tags.name == ^arg(:tag_name))
)
end
create :upsert do
upsert? true
argument :messages, {:array, :map}
argument :tags, {:array, :integer}
change manage_relationship(:messages, type: :direct_control)
change fn changeset, _ ->
Ash.Changeset.after_action(changeset, fn changeset, thread ->
tags = Ash.Changeset.get_argument(changeset, :tags) || []
# Not optimized in `manage_relationship`
# bulk actions should make this unnecessary
to_delete =
from thread_tag in AshHq.Discord.ThreadTag,
where: thread_tag.thread_id == ^thread.id,
where: thread_tag.tag_id not in ^tags
AshHq.Repo.delete_all(to_delete)
Enum.map(tags, fn tag ->
AshHq.Discord.ThreadTag.tag!(thread.id, tag) |> IO.inspect()
end)
{:ok, thread}
end)
end
end
end
code_interface do
define_for AshHq.Discord
define :upsert
define :by_id, action: :read, get_by: [:id]
define :feed, args: [:channel]
end
attributes do
integer_primary_key :id, generated?: false, writable?: true
attribute :type, :integer
attribute :name, :string do
allow_nil? false
end
attribute :author, :string do
allow_nil? false
end
attribute :create_timestamp, :utc_datetime do
allow_nil? false
end
end
relationships do
has_many :messages, AshHq.Discord.Message
belongs_to :channel, AshHq.Discord.Channel do
attribute_type :integer
allow_nil? false
attribute_writable? true
end
many_to_many :tags, AshHq.Discord.Tag do
through AshHq.Discord.ThreadTag
source_attribute_on_join_resource :thread_id
destination_attribute_on_join_resource :tag_id
end
end
end

View file

@ -0,0 +1,38 @@
defmodule AshHq.Discord.ThreadTag do
use Ash.Resource,
data_layer: AshPostgres.DataLayer
postgres do
table "discord_thread_tags"
repo AshHq.Repo
end
actions do
defaults [:read, :destroy]
create :tag do
upsert? true
end
end
code_interface do
define_for AshHq.Discord
define :tag, args: [:thread_id, :tag_id]
end
relationships do
belongs_to :thread, AshHq.Discord.Thread do
primary_key? true
allow_nil? false
attribute_writable? true
attribute_type :integer
end
belongs_to :tag, AshHq.Discord.Tag do
primary_key? true
allow_nil? false
attribute_writable? true
attribute_type :integer
end
end
end

View file

@ -0,0 +1,14 @@
defmodule AshHq.Discord.Supervisor do
use Supervisor
def start_link(args) do
Supervisor.start_link(__MODULE__, args, name: __MODULE__)
end
@impl true
def init(_init_arg) do
children = [AshHq.Discord.Listener]
Supervisor.init(children, strategy: :one_for_one)
end
end

View file

@ -75,6 +75,10 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown do
end
end
def as_html(nil, _, _, _, _) do
{:ok, nil, []}
end
def as_html(text, libraries, current_library, current_module, add_ids?) do
text
|> Earmark.as_html(opts(add_ids?))

View file

@ -53,6 +53,13 @@ defmodule AshHqWeb.Components.AppView.TopBar do
>
Blog
</a>
<a
href="/forum/showcase"
title="Forum"
class="text-lg font-bold px-2 md:px-4 dark:hover:text-primary-light-500 hover:text-primary-light-700"
>
Forum
</a>
<a
href="/media"
title="Media"

View file

@ -5,12 +5,13 @@ defmodule AshHqWeb.Components.Blog.Tag do
alias Surface.Components.LivePatch
prop tag, :string, required: true
prop prefix, :string
def render(assigns) do
~F"""
<LivePatch
to={"/blog?tag=#{@tag}"}
class="dark:bg-gray-700 bg-gray-300 rounded-lg px-2 text-lg max-w-min"
to={"#{@prefix}?tag=#{@tag}"}
class="dark:bg-gray-700 bg-gray-300 rounded-lg px-2 text-lg max-w-min whitespace-nowrap"
>
{@tag}
</LivePatch>

View file

@ -0,0 +1,37 @@
defmodule AshHqWeb.Components.Forum.Attachment do
use Surface.Component
prop attachment, :any, required: true
def render(assigns) do
~F"""
<div>
{#case video_type(@attachment.filename)}
{#match {:video, mime}}
<video controls width={@attachment.width} height={@attachment.height}>
<source src={@attachment.url} type={mime}>
</video>
{#match :image}
image
{#match _}
other
{/case}
</div>
"""
end
defp video_type(path) do
mime = MIME.from_path(path)
case mime do
"image/" <> _ ->
{:image, mime}
"video/" <> _ ->
{:video, mime}
_ ->
{:other, mime}
end
end
end

View file

@ -29,7 +29,7 @@ defmodule AshHqWeb.Pages.Blog do
<h1 class="mt-6 text-3xl font-semibold mb-4">{@post.title}</h1>
<div class="flex flex-row space-x-2 mb-4">
{#for tag <- @post.tag_names}
<Tag tag={tag} />
<Tag prefix="/blog" tag={tag} />
{/for}
</div>
<div class="flex flex-row items-center align-middle justify-between">
@ -70,7 +70,7 @@ defmodule AshHqWeb.Pages.Blog do
</div>
<div class="flex space-x-2">
{#for tag <- post.tag_names}
<Tag tag={tag} />
<Tag prefix="/blog" tag={tag} />
{/for}
</div>
</div>
@ -96,7 +96,7 @@ defmodule AshHqWeb.Pages.Blog do
<h3 class="text-lg font-bold mb-1">All Tags:</h3>
<div class="flex gap-2 flex-wrap w-full">
{#for tag <- @tags}
<Tag tag={to_string(tag.name)} />
<Tag prefix="/blog" tag={to_string(tag.name)} />
{/for}
</div>
</div>

View file

@ -12,29 +12,29 @@ defmodule AshHqWeb.Pages.Docs do
require Logger
require Ash.Query
prop(change_versions, :event, required: true)
prop(selected_versions, :map, required: true)
prop(libraries, :list, default: [])
prop(uri, :string)
prop(remove_version, :event)
prop(add_version, :event)
prop(change_version, :event)
prop(params, :map, required: true)
prop change_versions, :event, required: true
prop selected_versions, :map, required: true
prop libraries, :list, default: []
prop uri, :string
prop remove_version, :event
prop add_version, :event
prop change_version, :event
prop params, :map, required: true
data(library, :any)
data(extension, :any)
data(docs, :any)
data(library_version, :any)
data(guide, :any)
data(doc_path, :list, default: [])
data(dsls, :list, default: [])
data(dsl, :any)
data(options, :list, default: [])
data(module, :any)
data(mix_task, :any)
data(positional_options, :list)
data(description, :string)
data(title, :string)
data library, :any
data extension, :any
data docs, :any
data library_version, :any
data guide, :any
data doc_path, :list, default: []
data dsls, :list, default: []
data dsl, :any
data options, :list, default: []
data module, :any
data mix_task, :any
data positional_options, :list
data description, :string
data title, :string
@spec render(any) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do

View file

@ -0,0 +1,344 @@
defmodule AshHqWeb.Pages.Forum do
@moduledoc "Forum page"
use Surface.LiveComponent
require Ash.Query
alias AshHqWeb.Components.Blog.Tag
alias AshHqWeb.Components.Forum.Attachment
alias Surface.Components.LivePatch
import AshHqWeb.Tails
prop params, :map, default: %{}
data thread, :any, default: nil
data threads, :any, default: []
data tag, :string, default: nil
data tags, :any, default: []
data channels, :any, default: []
data channel, :any, default: []
def render(assigns) do
~F"""
<div class="container md:mx-auto">
<div class="flex flex-col md:flex-row md:pt-32 md:mx-32 min-h-screen">
<div class="w-full">
{#if @thread}
<div class="flex flex-row space-x-4 mb-6">
<LivePatch
class={classes([
"px-4 py-2 rounded-xl text-center border-2 bg-primary-light-300 dark:bg-primary-dark-200 border-primary-light-300 dark:text-black dark:border-primary-dark-200"
])}
to={"/forum/#{@channel.name}"}
>
<div class="flex flex-row">
<Heroicons.Outline.ArrowLeftIcon class="h-6 w-6 mr-2" />
Back to {String.capitalize(@channel.name)}
</div>
</LivePatch>
<a
_target="blank"
href={"discord://discordapp.com/channels/#{AshHq.Discord.Poller.server_id()}/#{@thread.id}"}
class="bg-primary-light-300 dark:bg-primary-dark-300 dark:text-black align-middle px-4 py-2 rounded-lg mt-2 md:mt-0"
>
<div class="flex flex-row items-center">
<span class="whitespace-nowrap">Discord App</span>
</div>
</a>
<a
_target="blank"
href={"https://discord.com/channels/#{AshHq.Discord.Poller.server_id()}/#{@thread.id}"}
class="bg-primary-light-300 dark:bg-primary-dark-300 dark:text-black align-middle px-4 py-2 rounded-lg mt-2 md:mt-0"
>
<div class="flex flex-row items-center">
<span class="whitespace-nowrap">Discord Web</span>
</div>
</a>
</div>
<head>
<meta property="og:title" content={@thread.name}>
<meta
property="og:description"
content={"See the forum discussion in the #{String.capitalize(@channel.name)} channel"}
/>
</head>
<div class="border shadow-sm rounded-lg px-8 pb-6 dark:border-gray-600 mb-4" ">
<h2 class="mt-6 text-3xl font-semibold mb-4">{@thread.name}</h2>
<div class="border-b pb-2">
<div>
{@thread.author}
</div>
<div>
{@thread.create_timestamp |> DateTime.to_date()}
</div>
<div class="flex space-x-2">
{#for tag <- @thread.tags}
<Tag prefix={"/forum/#{@channel.name}"} tag={tag.name} />
{/for}
</div>
</div>
<div class="divide-y divide-solid space-y-6 mt-4">
{#for message <- @thread.messages}
<div class="prose dark:prose-invert break-words">
<p>
{message.author}:
</p>{raw(message.content_html)}
{#for attachment <- message.attachments}
<Attachment attachment={attachment} />
{/for}
</div>
{/for}
</div>
</div>
{#else}
<h2 class="text-xl font-bold mt-2">
Channels
</h2>
<div class="flex flex-row space-x-4 mb-6">
{#for channel <- @channels}
<LivePatch
class={classes([
"px-4 py-2 rounded-xl text-center border-2 border-black dark:border-white",
"bg-primary-light-600 dark:bg-primary-dark-500 border-primary-light-600 dark:text-black dark:border-primary-dark-500":
@channel && @channel.id == channel.id
])}
to={"/forum/#{channel.name}"}
>
{String.capitalize(channel.name)}
</LivePatch>
{/for}
</div>
<div class="flex flex-row space-x-6 w-full justify-start ml-2 md:ml-0">
{#if @threads.offset != 0}
<LivePatch to={"/forum/#{@channel.name}?offset=#{min(@threads.offset - @threads.limit, 0)}"}>
<div class="px-4 py-2 rounded-xl text-center border-2 bg-primary-light-500 dark:bg-primary-dark-400 border-primary-light-500 dark:text-black dark:border-primary-dark-400">
Previous Page
</div>
</LivePatch>
{/if}
{#if @threads.more?}
<LivePatch to={"/forum/#{@channel.name}?offset=#{@threads.offset + @threads.limit}"}>
<div class="px-4 py-2 rounded-xl text-center border-2 bg-primary-light-500 dark:bg-primary-dark-400 border-primary-light-500 dark:text-black dark:border-primary-dark-400">
Next Page
</div>
</LivePatch>
{/if}
</div>
<head>
<meta property="og:title" content="Ash Framework Blog">
<meta
property="og:description"
content="A declarative foundation for ambitious Elixir applications. Model your domain, derive the rest."
/>
</head>
{#if @tag}
<h2 class="text-2xl font-semibold mb-1 mt-2">Showing {page_info(@threads)} with tag: {@tag}</h2>
{#else}
<h2 class="text-2xl font-semibold mb-1 mt-2">Showing {page_info(@threads)}</h2>
{/if}
<div>
{#for thread <- @threads.results}
<div class="border shadow-sm rounded-lg px-8 pb-6 dark:border-gray-600 mb-4" ">
<h2 class="mt-6 text-3xl font-semibold mb-4">{thread.name}</h2>
<div class="border-b pb-2">
<div>
{thread.author}
</div>
<div>
{thread.create_timestamp |> DateTime.to_date()}
</div>
<div class="flex flex-col md:flex-row items-center mt-2 py-2 space-x-2">
<LivePatch
to={"/forum/#{@channel.name}/#{thread.id}"}
class="bg-primary-light-600 dark:bg-primary-dark-500 dark:text-black align-middle px-4 py-2 rounded-lg"
>
<div class="flex flex-row items-center">
<span>Read</span><Heroicons.Solid.ArrowRightIcon class="h-4 w-4" />
</div>
</LivePatch>
<a
_target="blank"
href={"discord://discordapp.com/channels/#{AshHq.Discord.Poller.server_id()}/#{thread.id}"}
class="bg-primary-light-300 dark:bg-primary-dark-300 dark:text-black align-middle px-4 py-2 rounded-lg mt-2 md:mt-0"
>
<div class="flex flex-row items-center">
<span class="whitespace-nowrap">Discord App</span>
</div>
</a>
<a
_target="blank"
href={"https://discord.com/channels/#{AshHq.Discord.Poller.server_id()}/#{thread.id}"}
class="bg-primary-light-300 dark:bg-primary-dark-300 dark:text-black align-middle px-4 py-2 rounded-lg mt-2 md:mt-0"
>
<div class="flex flex-row items-center">
<span class="whitespace-nowrap">Discord Web</span>
</div>
</a>
</div>
<div class="flex space-x-2">
{#for tag <- thread.tags}
<Tag prefix={"/forum/#{@channel.name}"} tag={tag.name} />
{/for}
</div>
</div>
</div>
{/for}
</div>
<div class="flex flex-row space-x-6 w-full justify-start ml-2 md:ml-0">
{#if @threads.offset != 0}
<LivePatch to={"/forum/#{@channel.name}?offset=#{@threads.offset - @threads.limit}"}>
<div class="px-4 py-2 rounded-xl text-center border-2 bg-primary-light-500 dark:bg-primary-dark-400 border-primary-light-500 dark:text-black dark:border-primary-dark-400">
Previous Page
</div>
</LivePatch>
{/if}
{#if @threads.more?}
<LivePatch to={"/forum/#{@channel.name}?offset=#{@threads.offset + @threads.limit}"}>
<div class="px-4 py-2 rounded-xl text-center border-2 bg-primary-light-500 dark:bg-primary-dark-400 border-primary-light-500 dark:text-black dark:border-primary-dark-400">
Next Page
</div>
</LivePatch>
{/if}
</div>
{/if}
</div>
{#if !@thread}
<div class={classes(["flex flex-col px-4 md:pr-0 md:pl-4 md:w-3/12 space-y-6", "mt-9": !@thread])}>
<div class="border rounded-lg p-4 flex flex-col w-full dark:border-gray-600">
<h3 class="text-lg font-bold mb-1">All Tags:</h3>
<div class="flex gap-2 flex-wrap w-full">
{#for tag <- @tags}
<Tag prefix={"/forum/#{@channel.name}"} tag={tag} />
{/for}
</div>
</div>
</div>
{/if}
</div>
</div>
"""
end
def update(assigns, socket) do
{
:ok,
socket
|> assign(assigns)
|> assign_channels()
|> assign_channel()
|> assign_tags()
|> assign_tag()
|> assign_thread()
|> assign_threads()
}
end
defp assign_tags(socket) do
tags =
AshHq.Discord.Tag
# TODO: use distinct
# |> Ash.Query.distinct(:name)
|> Ash.Query.filter(channel_id == ^socket.assigns.channel.id)
|> Ash.Query.select(:name)
|> Ash.Query.sort(:name)
|> AshHq.Discord.read!()
|> Enum.map(&to_string(&1.name))
|> Enum.uniq_by(&String.downcase/1)
assign(socket, :tags, tags)
end
defp assign_tag(socket) do
tag =
if socket.assigns.params["tag"] do
Enum.find(
socket.assigns.tags,
&Ash.Type.CiString.equal?(&1, socket.assigns.params["tag"])
)
end
assign(socket, :tag, tag)
end
defp assign_thread(socket) do
if socket.assigns.params["id"] do
messages_query =
AshHq.Discord.Message
|> Ash.Query.sort(timestamp: :asc)
|> Ash.Query.deselect(:content)
|> Ash.Query.load(:attachments)
assign(
socket,
:thread,
AshHq.Discord.Thread.by_id!(socket.assigns.params["id"],
load: [:tags, messages: messages_query]
)
)
else
assign(socket, :thread, nil)
end
end
defp assign_threads(socket) do
assign(
socket,
:threads,
AshHq.Discord.Thread.feed!(
socket.assigns.channel.id,
%{tag_name: socket.assigns.tag},
page: [offset: String.to_integer(socket.assigns.params["offset"] || "0"), count: true],
load: :tags
)
)
end
defp assign_channels(socket) do
assign(socket, :channels, AshHq.Discord.Channel.read!() |> Enum.sort_by(& &1.order))
end
defp assign_channel(socket) do
channel =
Enum.find(
socket.assigns.channels,
&(&1.name == socket.assigns.params["channel"])
)
if is_nil(channel) do
raise Ash.Error.Query.NotFound.exception(
primary_key: %{name: socket.assigns.params["channel"]}
)
end
assign(
socket,
:channel,
channel
)
end
defp page_info(%{results: []}) do
"no threads "
end
defp page_info(%{more?: false, offset: 0, count: count}) do
"all #{count} threads "
end
defp page_info(%{more?: false, results: results, count: count}) do
"the last #{Enum.count(results)} of #{count} threads "
end
defp page_info(%{offset: 0, limit: limit, count: count}) do
"the first #{limit} of #{count} threads "
end
defp page_info(%{offset: offset, limit: limit, count: count}) do
"threads #{offset + 1} to #{offset + limit} of #{count}"
end
end

View file

@ -51,6 +51,8 @@ defmodule AshHqWeb.Router do
live("/media", AppViewLive, :media)
live("/blog", AppViewLive, :blog)
live("/blog/:slug", AppViewLive, :blog)
live("/forum/:channel", AppViewLive, :forum)
live("/forum/:channel/:id", AppViewLive, :forum)
live("/docs/", AppViewLive, :docs_dsl)
live("/docs/guides/:library/:version/*guide", AppViewLive, :docs_dsl)
live("/docs/dsl/:library", AppViewLive, :docs_dsl)

View file

@ -5,29 +5,29 @@ defmodule AshHqWeb.AppViewLive do
alias AshHqWeb.Components.AppView.TopBar
alias AshHqWeb.Components.{CatalogueModal, Search}
alias AshHqWeb.Pages.{Blog, Docs, Home, Media, UserSettings}
alias AshHqWeb.Pages.{Blog, Docs, Forum, Home, Media, UserSettings}
alias Phoenix.LiveView.JS
alias Surface.Components.Context
require Ash.Query
import AshHqWeb.Tails
data(configured_theme, :string, default: :system)
data(selected_versions, :map, default: %{})
data(libraries, :list, default: [])
data(selected_types, :map, default: %{})
data(current_user, :map)
data configured_theme, :string, default: :system
data selected_versions, :map, default: %{}
data libraries, :list, default: []
data selected_types, :map, default: %{}
data current_user, :map
data(library, :any, default: nil)
data(extension, :any, default: nil)
data(docs, :any, default: nil)
data(library_version, :any, default: nil)
data(guide, :any, default: nil)
data(doc_path, :list, default: [])
data(dsls, :list, default: [])
data(dsl, :any, default: nil)
data(options, :list, default: [])
data(module, :any, default: nil)
data library, :any, default: nil
data extension, :any, default: nil
data docs, :any, default: nil
data library_version, :any, default: nil
data guide, :any, default: nil
data doc_path, :list, default: []
data dsls, :list, default: []
data dsl, :any, default: nil
data options, :list, default: []
data module, :any, default: nil
def render(%{platform: :ios} = assigns) do
~F"""
@ -47,7 +47,7 @@ defmodule AshHqWeb.AppViewLive do
>
<head>
<meta property="og:type" content="text/html">
<meta property="og:image" content="https://ash-hq.org/images/ash-logo.png">
<meta property="og:image" content="https://ash-hq.org/images/ash-logo-side-big.svg">
<meta property="og:url" content={to_string(@uri)}>
<meta property="og:site_name" content="Ash HQ">
<meta property="twitter:card" content="summary_large_image">
@ -56,7 +56,7 @@ defmodule AshHqWeb.AppViewLive do
<!-- Need to adjust this for future blog writers -->
<meta property="twitter:creator" content="@ZachSDaniel1">
{#if @live_action not in [:docs_dsl, :blog]}
{#if @live_action not in [:docs_dsl, :blog, :forum]}
<meta property="og:title" content="Ash Framework">
<meta
property="og:description"
@ -120,6 +120,8 @@ defmodule AshHqWeb.AppViewLive do
<UserSettings id="user_settings" current_user={@current_user} />
{#match :media}
<Media id="media" />
{#match :forum}
<Forum id="forum" params={@params} />
{/case}
{#if @live_action != :docs_dsl}

View file

@ -47,19 +47,23 @@ defmodule AshHq.MixProject do
{:ash_json_api, github: "ash-project/ash_json_api"},
{:ash_authentication, github: "team-alembic/ash_authentication", override: true},
{:ash_authentication_phoenix, github: "team-alembic/ash_authentication_phoenix"},
{:absinthe_plug, "~> 1.5"},
{:ash_blog, github: "ash-project/ash_blog"},
{:ash_csv, github: "ash-project/ash_csv"},
# Discord
{:nostrum, github: "zachdaniel/nostrum"},
{:cowlib, "~> 2.11", hex: :remedy_cowlib, override: true},
# UI
{:tails, "~> 0.1"},
{:sunflower_ui, github: "zachdaniel/sunflower_ui"},
{:earmark, "~> 1.5.0-pre1", override: true},
# Other
{:absinthe_plug, "~> 1.5"},
{:nimble_options, "~> 0.5.1", override: true},
{:spark, "~> 0.3", override: true},
{:surface, "~> 0.9.1"},
{:surface_heroicons, "~> 0.6.0"},
{:ua_inspector, "~> 3.0"},
# Syntax Highlighting
{:elixir_sense, github: "elixir-lsp/elixir_sense", only: [:dev, :test]},
{:makeup, "~> 1.1"},
{:makeup_elixir, "~> 0.16.0"},
{:makeup_graphql, "~> 0.1.2"},

View file

@ -16,6 +16,7 @@
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"chacha20": {:hex, :chacha20, "1.0.4", "0359d8f9a32269271044c1b471d5cf69660c362a7c61a98f73a05ef0b5d9eb9e", [:mix], [], "hexpm", "2027f5d321ae9903f1f0da7f51b0635ad6b8819bc7fe397837930a2011bc2349"},
"cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
@ -23,9 +24,10 @@
"conv_case": {:hex, :conv_case, "0.2.3", "c1455c27d3c1ffcdd5f17f1e91f40b8a0bc0a337805a6e8302f441af17118ed8", [:mix], [], "hexpm", "88f29a3d97d1742f9865f7e394ed3da011abb7c5e8cc104e676fdef6270d4b4a"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"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.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"cowlib": {:hex, :remedy_cowlib, "2.11.1", "7abb4d0779a7d1c655f7642dc0bd0af754951e95005dfa01b500c68fe35a5961", [:rebar3], [], "hexpm", "0b613dc308e080cb6134285f1b1b55c3873e101652e70c70010fc6651c91b130"},
"credo": {:hex, :credo, "1.6.6", "f51f8d45db1af3b2e2f7bee3e6d3c871737bda4a91bff00c5eec276517d1a19c", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "625520ce0984ee0f9f1f198165cd46fa73c1e59a17ebc520038b8fce056a5bdc"},
"csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"},
"curve25519": {:hex, :curve25519, "1.0.5", "f801179424e4012049fcfcfcda74ac04f65d0ffceeb80e7ef1d3352deb09f5bb", [:mix], [], "hexpm", "0fba3ad55bf1154d4d5fc3ae5fb91b912b77b13f0def6ccb3a5d58168ff4192d"},
"dataloader": {:hex, :dataloader, "1.0.10", "a42f07641b1a0572e0b21a2a5ae1be11da486a6790f3d0d14512d96ff3e3bbe9", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "54cd70cec09addf4b2ace14cc186a283a149fd4d3ec5475b155951bf33cd963f"},
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
@ -35,9 +37,11 @@
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 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", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.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", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
"ed25519": {:hex, :ed25519, "1.4.1", "479fb83c3e31987c9cad780e6aeb8f2015fb5a482618cdf2a825c9aff809afc4", [:mix], [], "hexpm", "0dacb84f3faa3d8148e81019ca35f9d8dcee13232c32c9db5c2fb8ff48c80ec7"},
"eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"},
"elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"},
"elixir_sense": {:git, "https://github.com/elixir-lsp/elixir_sense.git", "ed875265f54994911774ac05c3a1b9adb65b80e7", []},
"equivalex": {:hex, :equivalex, "1.0.3", "170d9a82ae066e0020dfe1cf7811381669565922eb3359f6c91d7e9a1124ff74", [:mix], [], "hexpm", "46fa311adb855117d36e461b9c0ad2598f72110ad17ad73d7533c78020e045fc"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
"ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"},
@ -47,9 +51,11 @@
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.10.2", "9ad27d68270d879f73f26604bb2e573d40f29bf0e907064a9a337f90a16a0312", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd8b11b282072cec2ef30852283949c248bd5d2820c88d8acc89402b81db7550"},
"floki": {:hex, :floki, "0.33.1", "f20f1eb471e726342b45ccb68edb9486729e7df94da403936ea94a794f072781", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "461035fd125f13fdf30f243c85a0b1e50afbec876cbf1ceefe6fddd2e6d712c6"},
"gen_stage": {:hex, :gen_stage, "1.1.2", "b1656cd4ba431ed02c5656fe10cb5423820847113a07218da68eae5d6a260c23", [:mix], [], "hexpm", "9e39af23140f704e2b07a3e29d8f05fd21c2aaf4088ff43cb82be4b9e3148d02"},
"gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"},
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
"git_ops": {:hex, :git_ops, "2.5.1", "94ab6e3bc69fe765a62cbdb09969016613a154dec8fc4f6ebae682f030451da9", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "1219edc8810dcea40472ec5b7ed04786a9e1b0e4e49d8642b0e1cdfb8a6ad261"},
"gun": {:hex, :remedy_gun, "2.0.1", "0f0caed812ed9e4da4f144df2d5bf73b0a99481d395ecde990a3791decf321c6", [:rebar3], [{:cowlib, "~> 2.11.1", [hex: :remedy_cowlib, repo: "hexpm", optional: false]}], "hexpm", "b6685a85fbd12b757f86809be1b3d88fcef365b77605cd5aa34db003294c446e"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
@ -58,6 +64,7 @@
"joken": {:hex, :joken, "2.5.0", "09be497d804b8115eb6f07615cef2e60c2a1008fb89dc0aef0d4c4b4609b99aa", [:mix], [{:jose, "~> 1.11.2", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "22b25c89617c5ed8ca7b31026340a25ea0f9ca7160f9706b79be9ed81fdf74e7"},
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
"json_xema": {:hex, :json_xema, "0.4.2", "85de190f597a98ce9da436b8a59c97ef561a6ab6017255df8b494babefd6fb10", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.11", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "5516213758667d21669e0d63ea287238d277519527bac6c02140a5e34c1fda80"},
"kcl": {:hex, :kcl, "1.4.2", "8b73a55a14899dc172fcb05a13a754ac171c8165c14f65043382d567922f44ab", [:mix], [{:curve25519, ">= 1.0.4", [hex: :curve25519, repo: "hexpm", optional: false]}, {:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:poly1305, "~> 1.0", [hex: :poly1305, repo: "hexpm", optional: false]}, {:salsa20, "~> 1.0", [hex: :salsa20, repo: "hexpm", optional: false]}], "hexpm", "9f083dd3844d902df6834b258564a82b21a15eb9f6acdc98e8df0c10feeabf05"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_eex": {:hex, :makeup_eex, "0.1.1", "89352d5da318d97ae27bbcc87201f274504d2b71ede58ca366af6a5fbed9508d", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.16", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d111a0994eaaab09ef1a4b3b313ef806513bb4652152c26c0d7ca2be8402a964"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
@ -73,6 +80,7 @@
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"nostrum": {:git, "https://github.com/zachdaniel/nostrum.git", "240a1bdb0b63b7e0a9ae065dc4a9841f17c4c778", []},
"parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.7.0-rc.1", "28d6591441347ba68da9750771cec6fe18ce040c91095a46d5d332804d5037d5", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "96d98dcf161b2784fd08a52fd480729a9eeae33773440b4e7a89d1e7e804af52"},
@ -89,9 +97,11 @@
"plug_content_security_policy": {:hex, :plug_content_security_policy, "0.2.1", "0a19c76307ad000b3757739c14b34b83ecccf7d0a3472e64e14797a20b62939b", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ceea10050671c0387c64526e2cb337ee08e12705c737eaed80439266df5b2e29"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
"poly1305": {:hex, :poly1305, "1.0.4", "7cdc8961a0a6e00a764835918cdb8ade868044026df8ef5d718708ea6cc06611", [:mix], [{:chacha20, "~> 1.0", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 1.0", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm", "e14e684661a5195e149b3139db4a1693579d4659d65bba115a307529c47dbc3b"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"premailex": {:hex, :premailex, "0.3.16", "25c0c9c969f0025bbfdb06834f8f0fbd46e5ec50f5c252e6492165802ffbd2a6", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:floki, "~> 0.19", [hex: :floki, repo: "hexpm", optional: false]}, {:meeseeks, "~> 0.11", [hex: :meeseeks, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "c6b042f89ca63025dfbe3ef54fdbbe9d5f043b7c33d8e58f43a41d13a9475111"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"salsa20": {:hex, :salsa20, "1.0.4", "404cbea1fa8e68a41bcc834c0a2571ac175580fec01cc38cc70c0fb9ffc87e9b", [:mix], [], "hexpm", "745ddcd8cfa563ddb0fd61e7ce48d5146279a2cf7834e1da8441b369fdc58ac6"},
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"},

View file

@ -0,0 +1,218 @@
defmodule AshHq.Repo.Migrations.MigrateResources43 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
create table(:discord_threads, primary_key: false) do
add :id, :bigint, null: false, primary_key: true
add :type, :bigint
add :name, :text, null: false
add :author, :text, null: false
add :create_timestamp, :utc_datetime, null: false
add :channel_id, :bigint, null: false
end
create table(:discord_thread_tags, primary_key: false) do
add :thread_id,
references(:discord_threads,
column: :id,
name: "discord_thread_tags_thread_id_fkey",
type: :bigint,
prefix: "public"
),
primary_key: true,
null: false
add :tag_id, :bigint, null: false, primary_key: true
end
create table(:discord_tags, primary_key: false) do
add :id, :bigint, null: false, primary_key: true
end
alter table(:discord_thread_tags) do
modify :tag_id,
references(:discord_tags,
column: :id,
prefix: "public",
name: "discord_thread_tags_tag_id_fkey",
type: :bigint
)
end
alter table(:discord_tags) do
add :name, :citext, null: false
add :channel_id, :bigint
end
create table(:discord_reactions, primary_key: false) do
add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true
add :count, :bigint, null: false
add :emoji, :text, null: false
add :message_id, :bigint, null: false
end
create table(:discord_messages, primary_key: false) do
add :id, :bigint, null: false, primary_key: true
end
alter table(:discord_reactions) do
modify :message_id,
references(:discord_messages,
column: :id,
prefix: "public",
name: "discord_reactions_message_id_fkey",
type: :bigint
)
end
create unique_index(:discord_reactions, [:emoji, :message_id],
name: "discord_reactions_unique_message_emoji_index"
)
alter table(:discord_messages) do
add :author, :text, null: false
add :content, :text
add :content_html, :text
add :timestamp, :utc_datetime, null: false
add :thread_id,
references(:discord_threads,
column: :id,
name: "discord_messages_thread_id_fkey",
type: :bigint,
prefix: "public"
),
null: false
end
create table(:discord_channels, primary_key: false) do
add :id, :bigint, null: false, primary_key: true
end
alter table(:discord_threads) do
modify :channel_id,
references(:discord_channels,
column: :id,
prefix: "public",
name: "discord_threads_channel_id_fkey",
type: :bigint
)
end
alter table(:discord_tags) do
modify :channel_id,
references(:discord_channels,
column: :id,
prefix: "public",
name: "discord_tags_channel_id_fkey",
type: :bigint
)
end
create unique_index(:discord_tags, [:name, :channel_id],
name: "discord_tags_unique_name_per_channel_index"
)
alter table(:discord_channels) do
add :name, :text, null: false
add :order, :bigint, null: false
end
create table(:discord_attachments, primary_key: false) do
add :id, :bigint, null: false, primary_key: true
add :filename, :text
add :size, :bigint
add :url, :text
add :proxy_url, :text
add :height, :bigint
add :width, :bigint
add :message_id,
references(:discord_messages,
column: :id,
name: "discord_attachments_message_id_fkey",
type: :bigint,
prefix: "public"
),
null: false
end
end
def down do
drop constraint(:discord_attachments, "discord_attachments_message_id_fkey")
drop table(:discord_attachments)
alter table(:discord_channels) do
remove :order
remove :name
end
drop_if_exists unique_index(:discord_tags, [:name, :channel_id],
name: "discord_tags_unique_name_per_channel_index"
)
drop constraint(:discord_tags, "discord_tags_channel_id_fkey")
alter table(:discord_tags) do
modify :channel_id, :bigint
end
drop constraint(:discord_threads, "discord_threads_channel_id_fkey")
alter table(:discord_threads) do
modify :channel_id, :bigint
end
drop table(:discord_channels)
drop constraint(:discord_messages, "discord_messages_thread_id_fkey")
alter table(:discord_messages) do
remove :thread_id
remove :timestamp
remove :content_html
remove :content
remove :author
end
drop_if_exists unique_index(:discord_reactions, [:emoji, :message_id],
name: "discord_reactions_unique_message_emoji_index"
)
drop constraint(:discord_reactions, "discord_reactions_message_id_fkey")
alter table(:discord_reactions) do
modify :message_id, :bigint
end
drop table(:discord_messages)
drop table(:discord_reactions)
alter table(:discord_tags) do
remove :channel_id
remove :name
end
drop constraint(:discord_thread_tags, "discord_thread_tags_tag_id_fkey")
alter table(:discord_thread_tags) do
modify :tag_id, :bigint
end
drop table(:discord_tags)
drop constraint(:discord_thread_tags, "discord_thread_tags_thread_id_fkey")
drop table(:discord_thread_tags)
drop table(:discord_threads)
end
end

View file

@ -0,0 +1,113 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "filename",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "size",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "url",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "proxy_url",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "height",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "width",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "discord_attachments_message_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "discord_messages"
},
"size": null,
"source": "message_id",
"type": "bigint"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "881EA45557EDAE9BEDA6B1499907FC4360ADFC433081EF5A517BAD2737142699",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "discord_attachments"
}

View file

@ -0,0 +1,49 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "order",
"type": "bigint"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "016FA4BD290C4F8B78EB8E76812E8C212E54D239C0F29723DD9F0C375610E11B",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "discord_channels"
}

View file

@ -0,0 +1,93 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "author",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "content",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "content_html",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "timestamp",
"type": "utc_datetime"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "discord_messages_thread_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "discord_threads"
},
"size": null,
"source": "thread_id",
"type": "bigint"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "B6769A4474069F35583B825B15DB3E90E89E9B5BE7746F3CDC01F6F63F91DFBE",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "discord_messages"
}

View file

@ -0,0 +1,83 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v4()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "count",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "emoji",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "discord_reactions_message_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "discord_messages"
},
"size": null,
"source": "message_id",
"type": "bigint"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "D5988E48113667274892255D4AC922235CB8B69E1F8F7510587324190CB773F9",
"identities": [
{
"base_filter": null,
"index_name": "discord_reactions_unique_message_emoji_index",
"keys": [
"emoji",
"message_id"
],
"name": "unique_message_emoji"
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "discord_reactions"
}

View file

@ -0,0 +1,73 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "citext"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "discord_tags_channel_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "discord_channels"
},
"size": null,
"source": "channel_id",
"type": "bigint"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "4BC43E070E42BD9B6DF346361EA7C569CEC386861E071BE60010802B3B7B1A4C",
"identities": [
{
"base_filter": null,
"index_name": "discord_tags_unique_name_per_channel_index",
"keys": [
"name",
"channel_id"
],
"name": "unique_name_per_channel"
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "discord_tags"
}

View file

@ -0,0 +1,67 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "discord_thread_tags_thread_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "discord_threads"
},
"size": null,
"source": "thread_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "discord_thread_tags_tag_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "discord_tags"
},
"size": null,
"source": "tag_id",
"type": "bigint"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "C15DF3DE538A5AEEB42B4EB683E5C1AD6582D4A778652030E9FB80F39989D8A7",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "discord_thread_tags"
}

View file

@ -0,0 +1,93 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "type",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "author",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "create_timestamp",
"type": "utc_datetime"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "discord_threads_channel_id_fkey",
"on_delete": null,
"on_update": null,
"schema": "public",
"table": "discord_channels"
},
"size": null,
"source": "channel_id",
"type": "bigint"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "44F703FEF49D5A94C72022E82B830E1580E218320783CFCF26BB5B9E6297CEF3",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "discord_threads"
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB