diff --git a/lib/nulla/activitypub.ex b/lib/nulla/activitypub.ex index 85647e1..2bea870 100644 --- a/lib/nulla/activitypub.ex +++ b/lib/nulla/activitypub.ex @@ -20,12 +20,12 @@ defmodule Nulla.ActivityPub do indexable: "toot:indexable", attributionDomains: %{"@id" => "toot:attributionDomains", "@type" => "@id"}, Hashtag: "as:Hashtag", - focalPoint: %{"@container" => "@list", "@id" => "toot:focalPoint"} + vcard: "http://www.w3.org/2006/vcard/ns#" ) ] end - @spec ap_user(String.t(), Nulla.Users.User.t()) :: Jason.OrderedObject.t() + @spec ap_user(String.t(), Nulla.Models.User.t()) :: Jason.OrderedObject.t() def ap_user(domain, user) do Jason.OrderedObject.new([ "@context": context(), @@ -81,4 +81,41 @@ defmodule Nulla.ActivityPub do "vcard:Address": user.location ]) end + + @spec note(String.t(), Nulla.Models.User.t(), Nulla.Models.Note.t()) :: Jason.OrderedObject.t() + def note(domain, user, note) do + Jason.OrderedObject.new([ + "@context": [ + "https://www.w3.org/ns/activitystreams", + Jason.OrderedObject.new( + sensitive: "as:sensitive" + ) + ], + id: "https://#{domain}/@#{user.username}/#{note.id}", + type: "Note", + summary: nil, + inReplyTo: nil, + published: note.inserted_at, + url: "https://#{domain}/@#{user.username}/#{note.id}", + attributedTo: "https://#{domain}/@#{user.username}", + to: [ + "https://www.w3.org/ns/activitystreams#Public" + ], + cc: [ + "https://#{domain}/@#{user.username}/followers" + ], + sensetive: false, + content: note.content, + contentMap: Jason.OrderedObject.new( + "#{note.language}": "

@rf@mastodonsocial.ru Вниманию новичкам!

Вам небольшое руководство о том, как импротировать пост, которого нет в вашей ленте.

" + ), + attachment: [ + Jason.OrderedObject.new( + type: "Document", + mediaType: "video/mp4", + url: "https://mastodon.ml/system/media_attachments/files/000/040/494/original/8c06de179c11daea.mp4" + ) + ] + ]) + end end diff --git a/lib/nulla/models/bookmarks.ex b/lib/nulla/models/bookmarks.ex new file mode 100644 index 0000000..b4e7a5b --- /dev/null +++ b/lib/nulla/models/bookmarks.ex @@ -0,0 +1,23 @@ +defmodule Nulla.Models.Bookmark do + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query + alias Nulla.Repo + alias Nulla.Models.Bookmark + + schema "bookmarks" do + field :url, :string + field :user_id, :id + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(bookmark, attrs) do + bookmark + |> cast(attrs, [:url, :user_id]) + |> validate_required([:url, :user_id]) + end + + def get_all_bookmarks!(user_id), do: Repo.all(from n in Bookmark, where: n.user_id == ^user_id) +end diff --git a/lib/nulla/models/follow.ex b/lib/nulla/models/follow.ex new file mode 100644 index 0000000..0469dbd --- /dev/null +++ b/lib/nulla/models/follow.ex @@ -0,0 +1,19 @@ +defmodule Nulla.Models.Follow do + use Ecto.Schema + import Ecto.Changeset + + schema "follows" do + belongs_to :user, Nulla.Models.User + belongs_to :target, Nulla.Models.User + + timestamps() + end + + @doc false + def changeset(follow, attrs) do + follow + |> cast(attrs, [:user_id, :target_id]) + |> validate_required([:user_id, :target_id]) + |> unique_constraint([:user_id, :target_id]) + end +end diff --git a/lib/nulla/models/hashtag.ex b/lib/nulla/models/hashtag.ex new file mode 100644 index 0000000..0be647d --- /dev/null +++ b/lib/nulla/models/hashtag.ex @@ -0,0 +1,19 @@ +defmodule Nulla.Models.Hashtag do + use Ecto.Schema + import Ecto.Changeset + + schema "hashtags" do + field :tag, :string + field :usage_count, :integer, default: 0 + + timestamps() + end + + @doc false + def changeset(hashtag, attrs) do + hashtag + |> cast(attrs, [:tag, :usage_count]) + |> validate_required([:tag]) + |> unique_constraint(:tag) + end +end diff --git a/lib/nulla/instance_settings.ex b/lib/nulla/models/instance_settings.ex similarity index 64% rename from lib/nulla/instance_settings.ex rename to lib/nulla/models/instance_settings.ex index 3e4f495..96280c1 100644 --- a/lib/nulla/instance_settings.ex +++ b/lib/nulla/models/instance_settings.ex @@ -1,7 +1,8 @@ -defmodule Nulla.InstanceSettings do +defmodule Nulla.Models.InstanceSettings do use Ecto.Schema import Ecto.Changeset alias Nulla.Repo + alias Nulla.Models.InstanceSettings schema "instance_settings" do field :name, :string, default: "Nulla" @@ -10,14 +11,16 @@ defmodule Nulla.InstanceSettings do field :registration, :boolean, default: false field :max_characters, :integer, default: 5000 field :max_upload_size, :integer, default: 50 + field :public_key, :string + field :private_key, :string end @doc false def changeset(instance_settings, attrs) do instance_settings - |> cast(attrs, [:name, :description, :domain, :registration, :max_characters, :max_upload_size]) - |> validate_required([:name, :description, :domain, :registration, :max_characters, :max_upload_size]) + |> cast(attrs, [:name, :description, :domain, :registration, :max_characters, :max_upload_size, :public_key, :private_key]) + |> validate_required([:name, :description, :domain, :registration, :max_characters, :max_upload_size, :public_key, :private_key]) end - def get_instance_settings!, do: Repo.one!(Nulla.InstanceSettings) + def get_instance_settings!, do: Repo.one!(InstanceSettings) end diff --git a/lib/nulla/models/media_attachment.ex b/lib/nulla/models/media_attachment.ex new file mode 100644 index 0000000..9eca245 --- /dev/null +++ b/lib/nulla/models/media_attachment.ex @@ -0,0 +1,20 @@ +defmodule Nulla.Models.MediaAttachment do + use Ecto.Schema + import Ecto.Changeset + + schema "media_attachments" do + field :file, :string + field :mime_type, :string + field :description, :string + + belongs_to :note, Nulla.Models.Note + + timestamps(type: :utc_datetime) + end + + def changeset(media, attrs) do + media + |> cast(attrs, [:note_id, :file, :mime_type, :description]) + |> validate_required([:note_id, :file]) + end +end diff --git a/lib/nulla/models/moderation_log.ex b/lib/nulla/models/moderation_log.ex new file mode 100644 index 0000000..f6d3469 --- /dev/null +++ b/lib/nulla/models/moderation_log.ex @@ -0,0 +1,23 @@ +defmodule Nulla.Models.ModerationLog do + use Ecto.Schema + import Ecto.Changeset + + schema "moderation_logs" do + field :target_type, :string + field :target_id, :string + field :action, :string + field :reason, :string + field :metadata, :map + + belongs_to :moderator, Nulla.Models.User + + timestamps() + end + + @doc false + def changeset(moderation_log, attrs) do + moderation_log + |> cast(attrs, [:moderator_id, :target_type, :target_id, :action, :reason, :metadata]) + |> validate_required([:moderator_id, :target_type, :target_id, :action]) + end +end diff --git a/lib/nulla/models/note.ex b/lib/nulla/models/note.ex new file mode 100644 index 0000000..f4d5960 --- /dev/null +++ b/lib/nulla/models/note.ex @@ -0,0 +1,29 @@ +defmodule Nulla.Models.Note do + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query + alias Nulla.Repo + alias Nulla.Models.Note + + schema "notes" do + field :content, :string + field :visibility, Ecto.Enum, values: [:public, :unlisted, :followers, :private], default: :public + field :sensitive, :boolean, default: false + field :language, :string + field :in_reply_to, :string + + belongs_to :user, Nulla.Models.User + has_many :media_attachments, Nulla.Models.MediaAttachment + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(note, attrs) do + note + |> cast(attrs, [:content, :visibility, :sensitive, :language, :in_reply_to, :user_id]) + |> validate_required([:content, :visibility, :sensitive, :language, :in_reply_to, :user_id]) + end + + def get_all_notes!(user_id), do: Repo.all(from n in Note, where: n.user_id == ^user_id) +end diff --git a/lib/nulla/models/notification.ex b/lib/nulla/models/notification.ex new file mode 100644 index 0000000..c0d595c --- /dev/null +++ b/lib/nulla/models/notification.ex @@ -0,0 +1,22 @@ +defmodule Nulla.Models.Notification do + use Ecto.Schema + import Ecto.Changeset + + schema "notifications" do + field :type, :string + field :data, :map + field :read, :boolean, default: false + + belongs_to :user, Nulla.Models.User + belongs_to :actor, Nulla.Models.User + + timestamps() + end + + @doc false + def changeset(notification, attrs) do + notification + |> cast(attrs, [:user_id, :actor_id, :type, :data, :read]) + |> validate_required([:user_id, :type]) + end +end diff --git a/lib/nulla/models/session.ex b/lib/nulla/models/session.ex new file mode 100644 index 0000000..c505ad6 --- /dev/null +++ b/lib/nulla/models/session.ex @@ -0,0 +1,21 @@ +defmodule Nulla.Models.Session do + use Ecto.Schema + import Ecto.Changeset + + schema "sessions" do + field :token, :string + field :user_agent, :string + field :ip, :string + + belongs_to :user, Nulla.Models.User + + timestamps(type: :utc_datetime) + end + + def changeset(session, attrs) do + session + |> cast(attrs, [:user_id, :token, :user_agent, :ip]) + |> validate_required([:user_id, :token]) + |> unique_constraint(:token) + end +end diff --git a/lib/nulla/users/user.ex b/lib/nulla/models/user.ex similarity index 80% rename from lib/nulla/users/user.ex rename to lib/nulla/models/user.ex index b03f068..d350ad0 100644 --- a/lib/nulla/users/user.ex +++ b/lib/nulla/models/user.ex @@ -1,13 +1,14 @@ -defmodule Nulla.Users.User do +defmodule Nulla.Models.User do use Ecto.Schema import Ecto.Changeset + alias Nulla.Repo + alias Nulla.Models.User schema "users" do field :username, :string field :email, :string field :password, :string field :is_moderator, :boolean, default: false - field :realname, :string field :bio, :string field :location, :string @@ -24,6 +25,10 @@ defmodule Nulla.Users.User do field :avatar, :string field :banner, :string + has_many :user_sessions, Nulla.Models.Session + has_many :notes, Nulla.Models.Note + has_many :media_attachments, through: [:notes, :media_attachments] + timestamps(type: :utc_datetime) end @@ -33,4 +38,6 @@ defmodule Nulla.Users.User do |> cast(attrs, [:username, :email, :password, :is_moderator, :realname, :bio, :location, :birthday, :fields, :follow_approval, :is_bot, :is_discoverable, :is_indexable, :is_memorial, :private_key, :public_key, :avatar, :banner]) |> validate_required([:username, :email, :password, :is_moderator, :realname, :bio, :location, :birthday, :fields, :follow_approval, :is_bot, :is_discoverable, :is_indexable, :is_memorial, :private_key, :public_key, :avatar, :banner]) end + + def get_user_by_username!(username), do: Repo.get_by!(User, username: username) end diff --git a/lib/nulla/users.ex b/lib/nulla/users.ex deleted file mode 100644 index 292db75..0000000 --- a/lib/nulla/users.ex +++ /dev/null @@ -1,106 +0,0 @@ -defmodule Nulla.Users do - @moduledoc """ - The Users context. - """ - - import Ecto.Query, warn: false - alias Nulla.Repo - - alias Nulla.Users.User - - @doc """ - Returns the list of users. - - ## Examples - - iex> list_users() - [%User{}, ...] - - """ - def list_users do - Repo.all(User) - end - - @doc """ - Gets a single user. - - Raises `Ecto.NoResultsError` if the User does not exist. - - ## Examples - - iex> get_user!(123) - %User{} - - iex> get_user!(456) - ** (Ecto.NoResultsError) - - """ - def get_user!(id), do: Repo.get!(User, id) - - def get_user_by_username!(username), do: Repo.get_by!(User, username: username) - - @doc """ - Creates a user. - - ## Examples - - iex> create_user(%{field: value}) - {:ok, %User{}} - - iex> create_user(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_user(attrs \\ %{}) do - %User{} - |> User.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a user. - - ## Examples - - iex> update_user(user, %{field: new_value}) - {:ok, %User{}} - - iex> update_user(user, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_user(%User{} = user, attrs) do - user - |> User.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a user. - - ## Examples - - iex> delete_user(user) - {:ok, %User{}} - - iex> delete_user(user) - {:error, %Ecto.Changeset{}} - - """ - def delete_user(%User{} = user) do - Repo.delete(user) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking user changes. - - ## Examples - - iex> change_user(user) - %Ecto.Changeset{data: %User{}} - - """ - def change_user(%User{} = user, attrs \\ %{}) do - User.changeset(user, attrs) - end -end diff --git a/lib/nulla_web.ex b/lib/nulla_web.ex index 862aea6..9feaa88 100644 --- a/lib/nulla_web.ex +++ b/lib/nulla_web.ex @@ -17,7 +17,7 @@ defmodule NullaWeb do those modules here. """ - def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + def static_paths, do: ~w(assets files fonts images favicon.ico robots.txt) def router do quote do diff --git a/lib/nulla_web/controllers/note_controller.ex b/lib/nulla_web/controllers/note_controller.ex new file mode 100644 index 0000000..814a1dd --- /dev/null +++ b/lib/nulla_web/controllers/note_controller.ex @@ -0,0 +1,62 @@ +defmodule NullaWeb.NoteController do + use NullaWeb, :controller + + alias Nulla.Notes + alias Nulla.Models.Note + + def index(conn, _params) do + notes = Notes.list_notes() + render(conn, :index, notes: notes) + end + + def new(conn, _params) do + changeset = Notes.change_note(%Note{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"note" => note_params}) do + case Notes.create_note(note_params) do + {:ok, note} -> + conn + |> put_flash(:info, "Note created successfully.") + |> redirect(to: ~p"/notes/#{note}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + note = Notes.get_note!(id) + render(conn, :show, note: note) + end + + def edit(conn, %{"id" => id}) do + note = Notes.get_note!(id) + changeset = Notes.change_note(note) + render(conn, :edit, note: note, changeset: changeset) + end + + def update(conn, %{"id" => id, "note" => note_params}) do + note = Notes.get_note!(id) + + case Notes.update_note(note, note_params) do + {:ok, note} -> + conn + |> put_flash(:info, "Note updated successfully.") + |> redirect(to: ~p"/notes/#{note}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, note: note, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + note = Notes.get_note!(id) + {:ok, _note} = Notes.delete_note(note) + + conn + |> put_flash(:info, "Note deleted successfully.") + |> redirect(to: ~p"/notes") + end +end diff --git a/lib/nulla_web/controllers/note_html.ex b/lib/nulla_web/controllers/note_html.ex new file mode 100644 index 0000000..447c9ab --- /dev/null +++ b/lib/nulla_web/controllers/note_html.ex @@ -0,0 +1,13 @@ +defmodule NullaWeb.NoteHTML do + use NullaWeb, :html + + embed_templates "note_html/*" + + @doc """ + Renders a note form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def note_form(assigns) +end diff --git a/lib/nulla_web/controllers/note_html/edit.html.heex b/lib/nulla_web/controllers/note_html/edit.html.heex new file mode 100644 index 0000000..3bef388 --- /dev/null +++ b/lib/nulla_web/controllers/note_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Note {@note.id} + <:subtitle>Use this form to manage note records in your database. + + +<.note_form changeset={@changeset} action={~p"/notes/#{@note}"} /> + +<.back navigate={~p"/notes"}>Back to notes diff --git a/lib/nulla_web/controllers/note_html/index.html.heex b/lib/nulla_web/controllers/note_html/index.html.heex new file mode 100644 index 0000000..ffeedbc --- /dev/null +++ b/lib/nulla_web/controllers/note_html/index.html.heex @@ -0,0 +1,23 @@ +<.header> + Listing Notes + <:actions> + <.link href={~p"/notes/new"}> + <.button>New Note + + + + +<.table id="notes" rows={@notes} row_click={&JS.navigate(~p"/notes/#{&1}")}> + <:col :let={note} label="Content">{note.content} + <:action :let={note}> +
+ <.link navigate={~p"/notes/#{note}"}>Show +
+ <.link navigate={~p"/notes/#{note}/edit"}>Edit + + <:action :let={note}> + <.link href={~p"/notes/#{note}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/lib/nulla_web/controllers/note_html/new.html.heex b/lib/nulla_web/controllers/note_html/new.html.heex new file mode 100644 index 0000000..4cf47a4 --- /dev/null +++ b/lib/nulla_web/controllers/note_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Note + <:subtitle>Use this form to manage note records in your database. + + +<.note_form changeset={@changeset} action={~p"/notes"} /> + +<.back navigate={~p"/notes"}>Back to notes diff --git a/lib/nulla_web/controllers/note_html/note_form.html.heex b/lib/nulla_web/controllers/note_html/note_form.html.heex new file mode 100644 index 0000000..da6ac0f --- /dev/null +++ b/lib/nulla_web/controllers/note_html/note_form.html.heex @@ -0,0 +1,9 @@ +<.simple_form :let={f} for={@changeset} action={@action}> + <.error :if={@changeset.action}> + Oops, something went wrong! Please check the errors below. + + <.input field={f[:content]} type="text" label="Content" /> + <:actions> + <.button>Save Note + + diff --git a/lib/nulla_web/controllers/note_html/show.html.heex b/lib/nulla_web/controllers/note_html/show.html.heex new file mode 100644 index 0000000..d7f2f70 --- /dev/null +++ b/lib/nulla_web/controllers/note_html/show.html.heex @@ -0,0 +1,15 @@ +<.header> + Note {@note.id} + <:subtitle>This is a note record from your database. + <:actions> + <.link href={~p"/notes/#{@note}/edit"}> + <.button>Edit note + + + + +<.list> + <:item title="Content">{@note.content} + + +<.back navigate={~p"/notes"}>Back to notes diff --git a/lib/nulla_web/controllers/user_controller.ex b/lib/nulla_web/controllers/user_controller.ex index 76e8b80..0f078e0 100644 --- a/lib/nulla_web/controllers/user_controller.ex +++ b/lib/nulla_web/controllers/user_controller.ex @@ -1,21 +1,23 @@ defmodule NullaWeb.UserController do use NullaWeb, :controller - alias Nulla.Users - alias Nulla.InstanceSettings + alias Nulla.Models.User + alias Nulla.Models.Note + alias Nulla.Models.InstanceSettings alias Nulla.ActivityPub def show(conn, %{"username" => username}) do accept = List.first(get_req_header(conn, "accept")) instance_settings = InstanceSettings.get_instance_settings!() domain = instance_settings.domain - user = Users.get_user_by_username!(username) + user = User.get_user_by_username!(username) + notes = Note.get_all_notes!(user.id) if accept in ["application/activity+json", "application/ld+json"] do conn |> put_resp_content_type("application/activity+json") |> json(ActivityPub.ap_user(domain, user)) else - render(conn, :show, user: user, domain: domain, layout: false) + render(conn, :show, domain: domain, user: user, notes: notes, layout: false) end end end diff --git a/lib/nulla_web/controllers/user_html.ex b/lib/nulla_web/controllers/user_html.ex index db0df5b..fe6103e 100644 --- a/lib/nulla_web/controllers/user_html.ex +++ b/lib/nulla_web/controllers/user_html.ex @@ -38,4 +38,42 @@ defmodule NullaWeb.UserHTML do "#{formatted} (#{relative})" end + + def format_note_datetime(datetime) do + Timex.format!(datetime, "{0D} {Mfull} {YYYY}, {h24}:{m}", :strftime) + end + + def format_note_datetime_diff(datetime) do + now = Timex.now() + diff = Timex.diff(now, datetime, :seconds) + + cond do + diff < 60 -> + "now" + + diff < 3600 -> + minutes = div(diff, 60) + "#{minutes}m ago" + + diff < 86400 -> + hours = div(diff, 3600) + "#{hours}h ago" + + diff < 518400 -> + days = div(diff, 86400) + "#{days}d ago" + + diff < 2419200 -> + weeks = div(diff, 604800) + "#{weeks}w ago" + + diff < 28512000 -> + months = Timex.diff(now, datetime, :months) + "#{months}mo ago" + + true -> + years = Timex.diff(now, datetime, :years) + "#{years}y ago" + end + end end diff --git a/lib/nulla_web/controllers/user_html/show.html.heex b/lib/nulla_web/controllers/user_html/show.html.heex index 708842f..9ce9f87 100644 --- a/lib/nulla_web/controllers/user_html/show.html.heex +++ b/lib/nulla_web/controllers/user_html/show.html.heex @@ -10,8 +10,11 @@
- - + +
+ + +
{@user.realname} @@ -26,14 +29,12 @@
<%= @user.location %>
<% end %> - <%= if @user.birthday do %>
<.icon name="hero-cake" class="mt-0.5 h-5 w-5 flex-none" />
<%= format_birthdate(@user.birthday) %>
<% end %> -
<.icon name="hero-calendar" class="mt-0.5 h-5 w-5 flex-none" />
@@ -59,12 +60,54 @@ 31 Followers
-
+
Featured Posts Posts and replies Media
+
+ <%= for note <- @notes do %> +
+
+ +
+
+
+ + <%= @user.realname %> + + + @<%= @user.username %>@<%= @domain %> + +
+
+ <%= case note.visibility do %> + <% :public -> %> + <.icon name="hero-globe-americas" class="h-5 w-5" /> + <% :unlisted -> %> + <.icon name="hero-moon" class="h-5 w-5" /> + <% :followers -> %> + <.icon name="hero-lock-closed" class="h-5 w-5" /> + <% :private -> %> + <.icon name="hero-at-symbol" class="h-5 w-5" /> + <% end %> + <%= format_note_datetime_diff(note.inserted_at) %> +
+
+
+

<%= note.content %>

+
+
+ + + +
+
+
+
+ <% end %> +
diff --git a/priv/repo/migrations/20250527054942_create_instance.exs b/priv/repo/migrations/20250527054942_create_instance_settings.exs similarity index 86% rename from priv/repo/migrations/20250527054942_create_instance.exs rename to priv/repo/migrations/20250527054942_create_instance_settings.exs index 4fe6dc8..b316d05 100644 --- a/priv/repo/migrations/20250527054942_create_instance.exs +++ b/priv/repo/migrations/20250527054942_create_instance_settings.exs @@ -9,6 +9,10 @@ defmodule Nulla.Repo.Migrations.CreateInstanceSettings do add :registration, :boolean, default: false, null: false add :max_characters, :integer, default: 5000, null: false add :max_upload_size, :integer, default: 50, null: false + add :public_key, :string + add :private_key, :string + + timestamps() end end end diff --git a/priv/repo/migrations/20250530110822_create_users.exs b/priv/repo/migrations/20250530110822_create_users.exs index 265c22c..54ab8cb 100644 --- a/priv/repo/migrations/20250530110822_create_users.exs +++ b/priv/repo/migrations/20250530110822_create_users.exs @@ -7,7 +7,6 @@ defmodule Nulla.Repo.Migrations.CreateUsers do add :email, :string add :password, :string add :is_moderator, :boolean, default: false, null: false - add :realname, :string add :bio, :string add :location, :string diff --git a/priv/repo/migrations/20250604083506_create_notes.exs b/priv/repo/migrations/20250604083506_create_notes.exs new file mode 100644 index 0000000..61ce44f --- /dev/null +++ b/priv/repo/migrations/20250604083506_create_notes.exs @@ -0,0 +1,18 @@ +defmodule Nulla.Repo.Migrations.CreateNotes do + use Ecto.Migration + + def change do + create table(:notes) do + add :content, :string + add :visibility, :string, default: "public" + add :sensitive, :boolean, default: false + add :language, :string + add :in_reply_to, :string + add :user_id, references(:users, on_delete: :delete_all) + + timestamps(type: :utc_datetime) + end + + create index(:notes, [:user_id]) + end +end diff --git a/priv/repo/migrations/20250606100445_create_bookmarks.exs b/priv/repo/migrations/20250606100445_create_bookmarks.exs new file mode 100644 index 0000000..810721e --- /dev/null +++ b/priv/repo/migrations/20250606100445_create_bookmarks.exs @@ -0,0 +1,14 @@ +defmodule Nulla.Repo.Migrations.CreateBookmarks do + use Ecto.Migration + + def change do + create table(:bookmarks) do + add :url, :string + add :user_id, references(:users, on_delete: :delete_all) + + timestamps(type: :utc_datetime) + end + + create index(:bookmarks, [:user_id]) + end +end diff --git a/priv/repo/migrations/20250606103230_create_notifications.exs b/priv/repo/migrations/20250606103230_create_notifications.exs new file mode 100644 index 0000000..85ec848 --- /dev/null +++ b/priv/repo/migrations/20250606103230_create_notifications.exs @@ -0,0 +1,18 @@ +defmodule Nulla.Repo.Migrations.CreateNotifications do + use Ecto.Migration + + def change do + create table(:notifications) do + add :user_id, references(:users, on_delete: :delete_all), null: false + add :actor_id, references(:users, on_delete: :nilify_all) + add :type, :string, null: false + add :data, :map + add :read, :boolean, default: false, null: false + + timestamps() + end + + create index(:notifications, [:user_id]) + create index(:notifications, [:actor_id]) + end +end diff --git a/priv/repo/migrations/20250606103527_create_moderations_logs.exs b/priv/repo/migrations/20250606103527_create_moderations_logs.exs new file mode 100644 index 0000000..252231f --- /dev/null +++ b/priv/repo/migrations/20250606103527_create_moderations_logs.exs @@ -0,0 +1,19 @@ +defmodule Nulla.Repo.Migrations.CreateModerationLogs do + use Ecto.Migration + + def change do + create table(:moderation_logs) do + add :moderator_id, references(:users, on_delete: :nilify_all), null: false + add :target_type, :string, null: false + add :target_id, :string, null: false + add :action, :string, null: false + add :reason, :text + add :metadata, :map + + timestamps() + end + + create index(:moderation_logs, [:moderator_id]) + create index(:moderation_logs, [:target_type, :target_id]) + end +end diff --git a/priv/repo/migrations/20250606103649_create_hashtags.exs b/priv/repo/migrations/20250606103649_create_hashtags.exs new file mode 100644 index 0000000..6a155cc --- /dev/null +++ b/priv/repo/migrations/20250606103649_create_hashtags.exs @@ -0,0 +1,14 @@ +defmodule Nulla.Repo.Migrations.CreateHashtags do + use Ecto.Migration + + def change do + create table(:hashtags) do + add :tag, :string, null: false + add :usage_count, :integer, default: 0, null: false + + timestamps() + end + + create unique_index(:hashtags, [:tag]) + end +end diff --git a/priv/repo/migrations/20250606103707_create_follows.exs b/priv/repo/migrations/20250606103707_create_follows.exs new file mode 100644 index 0000000..27f161f --- /dev/null +++ b/priv/repo/migrations/20250606103707_create_follows.exs @@ -0,0 +1,15 @@ +defmodule Nulla.Repo.Migrations.CreateFollows do + use Ecto.Migration + + def change do + create table(:follows) do + add :user_id, references(:users, on_delete: :delete_all), null: false + add :target_id, references(:users, on_delete: :delete_all), null: false + + timestamps() + end + + create unique_index(:follows, [:user_id, :target_id]) + create index(:follows, [:target_id]) + end +end diff --git a/priv/repo/migrations/20250606131715_create_sessions.exs b/priv/repo/migrations/20250606131715_create_sessions.exs new file mode 100644 index 0000000..b11c9fc --- /dev/null +++ b/priv/repo/migrations/20250606131715_create_sessions.exs @@ -0,0 +1,17 @@ +defmodule Nulla.Repo.Migrations.CreateSessions do + use Ecto.Migration + + def change do + create table(:sessions) do + add :user_id, references(:users, on_delete: :delete_all), null: false + add :token, :string, null: false + add :user_agent, :string + add :ip, :string + + timestamps(type: :utc_datetime) + end + + create index(:sessions, [:user_id]) + create unique_index(:sessions, [:token]) + end +end diff --git a/priv/repo/migrations/20250606132108_create_media_attachments.exs b/priv/repo/migrations/20250606132108_create_media_attachments.exs new file mode 100644 index 0000000..0aef7bd --- /dev/null +++ b/priv/repo/migrations/20250606132108_create_media_attachments.exs @@ -0,0 +1,16 @@ +defmodule Nulla.Repo.Migrations.CreateMediaAttachments do + use Ecto.Migration + + def change do + create table(:media_attachments) do + add :note_id, references(:notes, on_delete: :delete_all), null: false + add :file, :string, null: false + add :mime_type, :string + add :description, :string + + timestamps(type: :utc_datetime) + end + + create index(:media_attachments, [:note_id]) + end +end