diff --git a/lib/nulla/users.ex b/lib/nulla/users.ex new file mode 100644 index 0000000..292db75 --- /dev/null +++ b/lib/nulla/users.ex @@ -0,0 +1,106 @@ +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/user.ex b/lib/nulla/users/user.ex similarity index 86% rename from lib/nulla/user.ex rename to lib/nulla/users/user.ex index ab732d6..b03f068 100644 --- a/lib/nulla/user.ex +++ b/lib/nulla/users/user.ex @@ -1,4 +1,4 @@ -defmodule Nulla.User do +defmodule Nulla.Users.User do use Ecto.Schema import Ecto.Changeset @@ -13,6 +13,7 @@ defmodule Nulla.User do field :location, :string field :birthday, :date field :fields, :map + field :tags, {:array, :string} field :follow_approval, :boolean, default: false field :is_bot, :boolean, default: false field :is_discoverable, :boolean, default: true @@ -20,7 +21,7 @@ defmodule Nulla.User do field :is_memorial, :boolean, default: false field :private_key, :string field :public_key, :string - field :avater, :string + field :avatar, :string field :banner, :string timestamps(type: :utc_datetime) @@ -29,7 +30,7 @@ defmodule Nulla.User do @doc false def changeset(user, attrs) do user - |> 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, :avater, :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, :avater, :banner]) + |> 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 end diff --git a/lib/nulla_web/controllers/user_controller.ex b/lib/nulla_web/controllers/user_controller.ex index b152141..76e8b80 100644 --- a/lib/nulla_web/controllers/user_controller.ex +++ b/lib/nulla_web/controllers/user_controller.ex @@ -1,64 +1,21 @@ defmodule NullaWeb.UserController do use NullaWeb, :controller + alias Nulla.Users + alias Nulla.InstanceSettings + alias Nulla.ActivityPub def show(conn, %{"username" => username}) do - accept = get_req_header(conn, "accept") |> List.first() || "" + 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) - if String.contains?(accept, "application/activity+json") or String.contains?(accept, "application/ld+json") do + if accept in ["application/activity+json", "application/ld+json"] do conn |> put_resp_content_type("application/activity+json") - |> json(%{ - id: "https://localhost/@#{username}", - type: "Person", - following: "https://localhost/@#{username}/following", - followers: "https://localhost/@#{username}/followers", - inbox: "https://localhost/@#{username}/inbox", - outbox: "https://localhost/@#{username}/outbox", - featured: "https://localhost/@#{username}/collections/featured", - preferredUsername: "miraikumiko", - name: "Mirai Kumiko", - summary: "Lol Kek Cheburek", - url: "https://localhost/@#{username}", - manuallyApprovesFollowers: false, - discoverable: true, - indexable: true, - published: "2025-05-05T00:00:00Z", - memorial: false, - publicKey: %{ - id: "https://localhost/@#{username}#main-key", - owner: "https://localhost/@#{username}", - publicKeyPem: "public key" - }, - tag: [ - %{ - type: "Hashtag", - href: "https://localhost/tags/linux", - name: "#linux" - } - ], - attachment: [ - %{ - type: "PropertyValue", - name: "Website", - value: "https://miraikumiko.com" - } - ], - endpoints: %{ - sharedInbox: "https://localhost/inbox" - }, - icon: %{ - type: "Image", - mediaType: "image/jpeg", - url: "url" - }, - image: %{ - type: "Image", - mediaType: "image/jpeg", - url: "url" - } - }) + |> json(ActivityPub.ap_user(domain, user)) else - render(conn, :show, username: username, layout: false) + render(conn, :show, user: user, domain: domain, layout: false) end end end diff --git a/lib/nulla_web/controllers/user_controller.ex.bak b/lib/nulla_web/controllers/user_controller.ex.bak new file mode 100644 index 0000000..093ba20 --- /dev/null +++ b/lib/nulla_web/controllers/user_controller.ex.bak @@ -0,0 +1,62 @@ +defmodule NullaWeb.UserController do + use NullaWeb, :controller + + alias Nulla.Users + alias Nulla.Users.User + + def index(conn, _params) do + users = Users.list_users() + render(conn, :index, users: users) + end + + def new(conn, _params) do + changeset = Users.change_user(%User{}) + render(conn, :new, changeset: changeset) + end + + def create(conn, %{"user" => user_params}) do + case Users.create_user(user_params) do + {:ok, user} -> + conn + |> put_flash(:info, "User created successfully.") + |> redirect(to: ~p"/users/#{user}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + user = Users.get_user!(id) + render(conn, :show, user: user) + end + + def edit(conn, %{"id" => id}) do + user = Users.get_user!(id) + changeset = Users.change_user(user) + render(conn, :edit, user: user, changeset: changeset) + end + + def update(conn, %{"id" => id, "user" => user_params}) do + user = Users.get_user!(id) + + case Users.update_user(user, user_params) do + {:ok, user} -> + conn + |> put_flash(:info, "User updated successfully.") + |> redirect(to: ~p"/users/#{user}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, user: user, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + user = Users.get_user!(id) + {:ok, _user} = Users.delete_user(user) + + conn + |> put_flash(:info, "User deleted successfully.") + |> redirect(to: ~p"/users") + end +end diff --git a/lib/nulla_web/controllers/user_html.ex b/lib/nulla_web/controllers/user_html.ex index 3c0c6ba..db0df5b 100644 --- a/lib/nulla_web/controllers/user_html.ex +++ b/lib/nulla_web/controllers/user_html.ex @@ -1,10 +1,41 @@ defmodule NullaWeb.UserHTML do - @moduledoc """ - This module contains pages rendered by UserController. - - See the `user_html` directory for all templates available. - """ use NullaWeb, :html embed_templates "user_html/*" + + @doc """ + Renders a user form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def user_form(assigns) + + def format_birthdate(date) do + formatted = Date.to_string(date) |> String.replace("-", "/") + age = Timex.diff(Timex.today(), date, :years) + "#{formatted} (#{age} years old)" + end + + def format_registration_date(date) do + now = Timex.now() + formatted = Date.to_string(date) |> String.replace("-", "/") + + diff = Timex.diff(now, date, :days) + + relative = + cond do + diff == 0 -> "today" + diff == 1 -> "1 day ago" + diff < 30 -> "#{diff} days ago" + diff < 365 -> + months = Timex.diff(now, date, :months) + if months == 1, do: "1 month ago", else: "#{months} months ago" + true -> + years = Timex.diff(now, date, :years) + if years == 1, do: "1 year ago", else: "#{years} years ago" + end + + "#{formatted} (#{relative})" + end end diff --git a/lib/nulla_web/controllers/user_html/edit.html.heex b/lib/nulla_web/controllers/user_html/edit.html.heex new file mode 100644 index 0000000..2f8aa66 --- /dev/null +++ b/lib/nulla_web/controllers/user_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit User {@user.id} + <:subtitle>Use this form to manage user records in your database. + + +<.user_form changeset={@changeset} action={~p"/users/#{@user}"} /> + +<.back navigate={~p"/users"}>Back to users diff --git a/lib/nulla_web/controllers/user_html/index.html.heex b/lib/nulla_web/controllers/user_html/index.html.heex new file mode 100644 index 0000000..9eca5b7 --- /dev/null +++ b/lib/nulla_web/controllers/user_html/index.html.heex @@ -0,0 +1,23 @@ +<.header> + Listing Users + <:actions> + <.link href={~p"/users/new"}> + <.button>New User + + + + +<.table id="users" rows={@users} row_click={&JS.navigate(~p"/users/#{&1}")}> + <:col :let={user} label="Username">{user.username} + <:action :let={user}> +
Cryptopunk in the past.
-Silent girl now and admin of this instance.
-Grew up on hacker culture, philosophy, good old movies and anime. That's why I love cyberpunk — modern philosophy and technolization in one bottle. I also use Linux on a first-name basis and can program.
-Can play shooters, chess and other games where strategy and psychological analysis of opponents are important.
-Bunnies and rabbits are superior!
+{@user.bio}