about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsefidel <contact@sefidel.net>2024-02-20 05:48:01 +0900
committersefidel <contact@sefidel.net>2024-02-20 06:09:18 +0900
commit7fe334c0f881b27db617aedaf233f6ae58d72278 (patch)
tree948c5e2f1493c6e7090da39174d2699a81d7a624
downloadnixpkgs-exotic-7fe334c0f881b27db617aedaf233f6ae58d72278.tar.gz
nixpkgs-exotic-7fe334c0f881b27db617aedaf233f6ae58d72278.zip
project: Initial commit
-rw-r--r--LICENSE661
-rw-r--r--README.md4
-rw-r--r--default.nix10
-rw-r--r--flake.lock80
-rw-r--r--flake.nix39
-rw-r--r--hydra/default.nix1
-rw-r--r--hydra/jobsets.nix34
-rw-r--r--hydra/spec.json24
-rw-r--r--pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch2390
-rw-r--r--pkgs/akkoma/0002-Bump-lockfile.patch203
-rw-r--r--pkgs/akkoma/0003-Fix-OAuth-consumer-mode.patch248
-rw-r--r--pkgs/akkoma/default.nix197
-rw-r--r--pkgs/akkoma/mix.nix1611
-rw-r--r--pkgs/default.nix5
14 files changed, 5507 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..be3f7b2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<https://www.gnu.org/licenses/>.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..30a5670
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+nixpkgs-exotic
+--------------
+
+Custom shared packages that doesn't fit in the upstream nixpkgs
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..f620865
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,10 @@
+(import
+  (
+    let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
+    fetchTarball {
+      url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
+      sha256 = lock.nodes.flake-compat.locked.narHash;
+    }
+  )
+  { src = ./.; }
+).defaultNix
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..4d1115d
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,80 @@
+{
+  "nodes": {
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1696426674,
+        "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-parts": {
+      "inputs": {
+        "nixpkgs-lib": "nixpkgs-lib"
+      },
+      "locked": {
+        "lastModified": 1706830856,
+        "narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1708373374,
+        "narHash": "sha256-yEyDvDj/YQc4GOZa/cXx6YUzexD8uv1rUvD6SJVr6UI=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "93e1c2d08467d1117ebac45689469613a9fe8453",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs-lib": {
+      "locked": {
+        "dir": "lib",
+        "lastModified": 1706550542,
+        "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
+        "type": "github"
+      },
+      "original": {
+        "dir": "lib",
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-compat": "flake-compat",
+        "flake-parts": "flake-parts",
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..f025190
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,39 @@
+{
+  description = "Custom shared packages that doesn't fit in the upstream nixpkgs";
+
+  inputs.nixpkgs.url = "github:NixOS/nixpkgs";
+  inputs.flake-parts.url = "github:hercules-ci/flake-parts";
+  inputs.flake-compat.url = "github:edolstra/flake-compat";
+  inputs.flake-compat.flake = false;
+
+  outputs  = { self, flake-parts, ... } @ inputs:
+    flake-parts.lib.mkFlake { inherit inputs; } {
+        systems = [ "aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux" ];
+
+        perSystem = { lib, system, ... }:
+          let
+            pkgs = import inputs.nixpkgs { inherit system; };
+
+            pkgsFor = pkgs: overlays:
+              import pkgs {
+                inherit system overlays;
+            };
+            exoticPkgs = (pkgsFor inputs.nixpkgs [ self.overlays.default ]).exoticPackages;
+          in
+          {
+            _module.args.pkgs = pkgs;
+
+            packages = exoticPkgs;
+          };
+
+        flake = {
+          overlays.default = final: prev: {
+            exoticPackages = import ./pkgs { pkgs = prev; };
+          };
+
+          hydraJobs = {
+            inherit (self) packages;
+          };
+        };
+    };
+}
diff --git a/hydra/default.nix b/hydra/default.nix
new file mode 100644
index 0000000..827748a
--- /dev/null
+++ b/hydra/default.nix
@@ -0,0 +1 @@
+{ nixpkgs-exotic }: (import nixpkgs-exotic).hydraJobs
diff --git a/hydra/jobsets.nix b/hydra/jobsets.nix
new file mode 100644
index 0000000..f426873
--- /dev/null
+++ b/hydra/jobsets.nix
@@ -0,0 +1,34 @@
+{ nixpkgs, ... }:
+
+let
+  pkgs = import nixpkgs {};
+
+  jobsets = {
+    main = {
+      enabled = 1;
+      hidden = false;
+      description = "main branch";
+      nixexprinput = "nixpkgs-exotic";
+      nixexprpath = "hydra/default.nix";
+      checkinterval = 300;
+      schedulingshares = 100;
+      enableemail = false;
+      emailoverride = "";
+      keepnr = 1;
+      inputs = {
+        nixpkgs-exotic = {
+          type = "git";
+          value = "https://git.exotic.sh/nixpkgs-exotic main";
+          emailresponsible = false;
+        };
+        nixpkgs = {
+          type = "git";
+          value = "https://github.com/NixOS/nixpkgs.git";
+          emailresponsible = false;
+        };
+      };
+    };
+  };
+in {
+  jobsets = pkgs.writeText "jobsets.json" (builtins.toJSON jobsets);
+}
diff --git a/hydra/spec.json b/hydra/spec.json
new file mode 100644
index 0000000..cf17ff4
--- /dev/null
+++ b/hydra/spec.json
@@ -0,0 +1,24 @@
+{
+    "enabled": 1,
+    "hidden": false,
+    "description": "Jobsets",
+    "nixexprinput": "nixpkgs-exotic",
+    "nixexprpath": "hydra/jobsets.nix",
+    "checkinterval": 300,
+    "schedulingshares": 100,
+    "enableemail": false,
+    "emailoverride": "",
+    "keepnr": 1,
+    "inputs": {
+        "nixpkgs-exotic": {
+            "type": "git",
+            "value": "https://git.exotic.sh/nixpkgs-exotic main",
+            "emailresponsible": false
+        },
+        "nixpkgs": {
+            "type": "git",
+            "value": "https://github.com/NixOS/nixpkgs.git",
+            "emailresponsible": false
+        }
+    }
+}
diff --git a/pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch b/pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch
new file mode 100644
index 0000000..d4cc790
--- /dev/null
+++ b/pkgs/akkoma/0001-Migrate-to-phoenix-1.7.patch
@@ -0,0 +1,2390 @@
+From ed0cdb8b2be55b9f33f93b3ef2c7e2fa46ba4eb3 Mon Sep 17 00:00:00 2001
+From: sefidel <contact@sefidel.net>
+Date: Mon, 19 Feb 2024 21:06:43 +0900
+Subject: [PATCH 1/3] Migrate to phoenix 1.7
+
+Original commit: https://akkoma.dev/AkkomaGang/akkoma/commit/6cb40bee26
+Backported to v3.10.4
+
+Signed-off-by: sefidel <contact@sefidel.net>
+---
+ .formatter.exs                                | 13 ++++-
+ .woodpecker/lint.yml                          | 55 ++++++++++++++++++
+ .woodpecker/test.yml                          | 40 ++++---------
+ CHANGELOG.md                                  | 10 +++-
+ config/config.exs                             | 16 +----
+ lib/mix/tasks/pleroma/emoji.ex                |  2 +
+ lib/mix/tasks/pleroma/user.ex                 | 15 +----
+ lib/phoenix/transports/web_socket/raw.ex      |  1 -
+ lib/pleroma/emails/admin_email.ex             |  9 ++-
+ lib/pleroma/emails/user_email.ex              | 22 ++-----
+ lib/pleroma/user.ex                           |  9 +--
+ lib/pleroma/web.ex                            | 29 ++++++++--
+ lib/pleroma/web/activity_pub/activity_pub.ex  |  5 ++
+ lib/pleroma/web/activity_pub/builder.ex       |  4 +-
+ lib/pleroma/web/activity_pub/utils.ex         | 15 ++---
+ .../web/activity_pub/views/user_view.ex       | 14 ++---
+ .../controllers/admin_api_controller.ex       |  4 +-
+ lib/pleroma/web/endpoint.ex                   |  2 +-
+ lib/pleroma/web/feed/user_controller.ex       |  2 +-
+ lib/pleroma/web/masto_fe_controller.ex        |  4 +-
+ .../controllers/auth_controller.ex            |  9 +--
+ .../web/mastodon_api/views/status_view.ex     |  2 +-
+ .../web/mastodon_api/views/tag_view.ex        |  3 +-
+ lib/pleroma/web/metadata/providers/feed.ex    |  6 +-
+ .../web/metadata/providers/twitter_card.ex    |  4 +-
+ lib/pleroma/web/o_auth/o_auth_controller.ex   |  4 +-
+ .../web/o_status/o_status_controller.ex       |  3 +-
+ lib/pleroma/web/plugs/http_signature_plug.ex  |  7 ++-
+ .../web/static_fe/static_fe_controller.ex     | 11 ++--
+ lib/pleroma/web/static_fe/static_fe_view.ex   |  1 -
+ .../frontend_switcher/switch.html.eex         |  2 +-
+ .../web/templates/feed/feed/tag.atom.eex      |  4 +-
+ .../web/templates/feed/feed/tag.rss.eex       |  2 +-
+ .../web/templates/feed/feed/user.atom.eex     |  6 +-
+ .../web/templates/feed/feed/user.rss.eex      |  6 +-
+ .../web/templates/masto_fe/fedibird.html.heex | 58 +++++++++++++++++++
+ .../masto_fe/fedibird.index.html.eex          | 35 -----------
+ .../templates/masto_fe/glitchsoc.html.heex    | 57 ++++++++++++++++++
+ .../masto_fe/glitchsoc.index.html.eex         | 35 -----------
+ .../templates/o_auth/mfa/recovery.html.eex    | 12 ++--
+ .../web/templates/o_auth/mfa/totp.html.eex    | 12 ++--
+ .../templates/o_auth/o_auth/consumer.html.eex |  2 +-
+ .../templates/o_auth/o_auth/register.html.eex | 10 ++--
+ .../web/templates/o_auth/o_auth/show.html.eex | 10 ++--
+ .../static_fe/static_fe/profile.html.eex      |  2 +-
+ .../twitter_api/password/reset.html.eex       |  2 +-
+ .../twitter_api/remote_follow/follow.html.eex |  2 +-
+ .../remote_follow/follow_login.html.eex       |  2 +-
+ .../remote_follow/follow_mfa.html.eex         |  2 +-
+ .../twitter_api/util/status_interact.html.eex |  2 +-
+ .../twitter_api/util/subscribe.html.eex       |  2 +-
+ .../controllers/remote_follow_controller.ex   |  2 +-
+ lib/pleroma/web/views/embed_view.ex           |  3 +-
+ lib/pleroma/web/views/masto_fe_view.ex        |  2 +-
+ mix.exs                                       |  7 ++-
+ mix.lock                                      | 12 ++--
+ test/pleroma/emails/admin_email_test.exs      |  3 +-
+ test/pleroma/emails/user_email_test.exs       | 10 +---
+ test/pleroma/integration/federation_test.exs  |  5 +-
+ test/pleroma/user_test.exs                    | 14 +----
+ .../activity_pub_controller_test.exs          | 10 ++--
+ .../web/activity_pub/activity_pub_test.exs    |  8 +++
+ .../controllers/admin_api_controller_test.exs |  3 +-
+ .../controllers/report_controller_test.exs    | 10 ++--
+ .../controllers/user_controller_test.exs      |  6 +-
+ test/pleroma/web/feed/tag_controller_test.exs |  8 +--
+ .../pleroma/web/feed/user_controller_test.exs | 12 ++--
+ .../mastodon_api/update_credentials_test.exs  |  2 +-
+ .../mastodon_api/views/status_view_test.exs   |  2 +-
+ .../metadata/providers/twitter_card_test.exs  |  3 +-
+ .../web/mongoose_im_controller_test.exs       | 16 ++---
+ .../web/o_auth/o_auth_controller_test.exs     | 12 ++--
+ .../remote_follow_controller_test.exs         | 42 ++++++--------
+ test/pleroma/web/uploader_controller_test.exs |  4 +-
+ test/support/conn_case.ex                     |  2 +-
+ test/support/data_case.ex                     |  2 +
+ 76 files changed, 431 insertions(+), 355 deletions(-)
+ create mode 100644 .woodpecker/lint.yml
+ create mode 100644 lib/pleroma/web/templates/masto_fe/fedibird.html.heex
+ delete mode 100644 lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
+ create mode 100644 lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
+ delete mode 100644 lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex
+
+diff --git a/.formatter.exs b/.formatter.exs
+index abd91dbbe..a96afe758 100644
+--- a/.formatter.exs
++++ b/.formatter.exs
+@@ -1,3 +1,14 @@
+ [
+-  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/repo/optional_migrations/**/*.exs", "priv/scrubbers/*.ex"]
++  import_deps: [:ecto, :ecto_sql, :phoenix],
++  subdirectories: ["priv/*/migrations"],
++  plugins: [Phoenix.LiveView.HTMLFormatter],
++  inputs: [
++    "mix.exs",
++    "*.{heex,ex,exs}",
++    "{config,lib,test}/**/*.{heex,ex,exs}",
++    "priv/*/seeds.exs",
++    "priv/repo/migrations/*.exs",
++    "priv/repo/optional_migrations/**/*.exs",
++    "priv/scrubbers/*.ex"
++  ]
+ ]
+diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
+new file mode 100644
+index 000000000..8308e57d7
+--- /dev/null
++++ b/.woodpecker/lint.yml
+@@ -0,0 +1,55 @@
++platform: linux/amd64
++
++variables:
++  - &scw-secrets
++    - SCW_ACCESS_KEY
++    - SCW_SECRET_KEY
++    - SCW_DEFAULT_ORGANIZATION_ID
++  - &setup-hex "mix local.hex --force && mix local.rebar --force"
++  - &on-release
++    when:
++      event:
++        - push
++        - tag
++      branch:
++        - develop
++        - stable
++        - refs/tags/v*
++        - refs/tags/stable-*
++  - &on-stable
++    when:
++      event:
++        - push
++        - tag
++      branch:
++        - stable
++        - refs/tags/stable-*
++  - &on-point-release
++    when:
++      event:
++        - push
++      branch:
++        - develop
++        - stable
++  - &on-pr-open
++    when:
++      event:
++        - pull_request
++
++  - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG"
++
++  - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)"
++  - &mix-clean "mix deps.clean --all && mix clean"
++
++pipeline:
++  lint:
++    image: akkoma/ci-base:1.15-otp26
++    <<: *on-pr-open
++    environment:
++      MIX_ENV: test
++    commands:
++      - mix local.hex --force
++      - mix local.rebar --force
++      - mix deps.get
++      - mix compile
++      - mix format --check-formatted
+diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
+index be8ea0dfa..3a8a1652e 100644
+--- a/.woodpecker/test.yml
++++ b/.woodpecker/test.yml
+@@ -1,5 +1,8 @@
+ platform: linux/amd64
+ 
++depends_on:
++  - lint
++
+ matrix:
+   ELIXIR_VERSION:
+     - 1.14
+@@ -69,15 +72,7 @@ services:
+       POSTGRES_PASSWORD: postgres
+ 
+ pipeline:
+-  lint:
+-    <<: *on-pr-open
+-    image: akkoma/ci-base:1.15
+-    commands:
+-    - mix local.hex --force
+-    - mix local.rebar --force
+-    - mix format --check-formatted
+-
+-  build:
++  test:
+     image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION}
+     <<: *on-pr-open
+     environment:
+@@ -91,24 +86,9 @@ pipeline:
+       - mix local.rebar --force
+       - mix deps.get
+       - mix compile
+-
+-  test:
+-    image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION}
+-    <<: *on-pr-open
+-    environment:
+-      MIX_ENV: test
+-      POSTGRES_DB: pleroma_test_${ELIXIR_VERSION}_${OTP_VERSION}
+-      POSTGRES_USER: postgres
+-      POSTGRES_PASSWORD: postgres
+-      DB_HOST: postgres
+-    commands:
+-    - mix local.hex --force
+-    - mix local.rebar --force
+-    - mix deps.get
+-    - mix compile
+-    - mix ecto.drop -f -q
+-    - mix ecto.create
+-    - mix ecto.migrate
+-    - mkdir -p test/tmp
+-    - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked
+-    - mix test --preload-modules --only mocked
++      - mix ecto.drop -f -q
++      - mix ecto.create
++      - mix ecto.migrate
++      - mkdir -p test/tmp
++      - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked
++      - mix test --preload-modules --only mocked
+diff --git a/CHANGELOG.md b/CHANGELOG.md
+index 92b3d1a71..a6c356bab 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -6,8 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+ 
+ ## Unreleased
+ 
++## Added
++- Full compatibility with Erlang OTP26
++- handling of GET /api/v1/preferences
++
++## Changed
++- OTP builds are now built on erlang OTP26
++- The base Phoenix framework is now updated to 1.7
++
+ ## Fixed
+-- Issue where a bad inbox URL could break federation
++- Documentation issue in which a non-existing nginx file was referenced
+ 
+ ## 2023.08
+ 
+diff --git a/config/config.exs b/config/config.exs
+index 3430ee4d7..0a3cbde3f 100644
+--- a/config/config.exs
++++ b/config/config.exs
+@@ -110,17 +110,6 @@
+     "xmpp"
+   ]
+ 
+-websocket_config = [
+-  path: "/websocket",
+-  serializer: [
+-    {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"},
+-    {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"}
+-  ],
+-  timeout: 60_000,
+-  transport_log: false,
+-  compress: false
+-]
+-
+ # Configures the endpoint
+ config :pleroma, Pleroma.Web.Endpoint,
+   url: [host: "localhost"],
+@@ -130,10 +119,7 @@
+       {:_,
+        [
+          {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
+-         {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
+-          {Phoenix.Transports.WebSocket,
+-           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
+-         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
++         {:_, Plug.Cowboy.Handler, {Pleroma.Web.Endpoint, []}}
+        ]}
+     ]
+   ],
+diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
+index 5dedf276a..89df511a7 100644
+--- a/lib/mix/tasks/pleroma/emoji.ex
++++ b/lib/mix/tasks/pleroma/emoji.ex
+@@ -235,6 +235,8 @@ def run(["gen-pack" | args]) do
+ 
+       IO.puts("#{pack_file} has been created with the #{name} pack")
+     end
++
++    Pleroma.Emoji.reload()
+   end
+ 
+   def run(["reload"]) do
+diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
+index 4ca1c28eb..1a8e866ef 100644
+--- a/lib/mix/tasks/pleroma/user.ex
++++ b/lib/mix/tasks/pleroma/user.ex
+@@ -11,6 +11,7 @@ defmodule Mix.Tasks.Pleroma.User do
+   alias Pleroma.UserInviteToken
+   alias Pleroma.Web.ActivityPub.Builder
+   alias Pleroma.Web.ActivityPub.Pipeline
++  use Pleroma.Web, :verified_routes
+ 
+   @shortdoc "Manages Pleroma users"
+   @moduledoc File.read!("docs/docs/administration/CLI_tasks/user.md")
+@@ -113,11 +114,7 @@ def run(["reset_password", nickname]) do
+          {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
+       shell_info("Generated password reset token for #{user.nickname}")
+ 
+-      IO.puts(
+-        "URL: #{Pleroma.Web.Router.Helpers.reset_password_url(Pleroma.Web.Endpoint,
+-        :reset,
+-        token.token)}"
+-      )
++      IO.puts("URL: #{~p[/api/v1/pleroma/password_reset/#{token.token}]}")
+     else
+       _ ->
+         shell_error("No local user #{nickname}")
+@@ -303,13 +300,7 @@ def run(["invite" | rest]) do
+          {:ok, invite} <- UserInviteToken.create_invite(options) do
+       shell_info("Generated user invite token " <> String.replace(invite.invite_type, "_", " "))
+ 
+-      url =
+-        Pleroma.Web.Router.Helpers.redirect_url(
+-          Pleroma.Web.Endpoint,
+-          :registration_page,
+-          invite.token
+-        )
+-
++      url = url(~p[/registration/#{invite.token}])
+       IO.puts(url)
+     else
+       error ->
+diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex
+index 8ed64eb16..72def9dff 100644
+--- a/lib/phoenix/transports/web_socket/raw.ex
++++ b/lib/phoenix/transports/web_socket/raw.ex
+@@ -26,7 +26,6 @@ def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do
+       conn
+       |> fetch_query_params
+       |> Transport.transport_log(opts[:transport_log])
+-      |> Transport.force_ssl(handler, endpoint, opts)
+       |> Transport.check_origin(handler, endpoint, opts)
+ 
+     case conn do
+diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
+index 88bc78aec..683de8e3b 100644
+--- a/lib/pleroma/emails/admin_email.ex
++++ b/lib/pleroma/emails/admin_email.ex
+@@ -6,10 +6,13 @@ defmodule Pleroma.Emails.AdminEmail do
+   @moduledoc "Admin emails"
+ 
+   import Swoosh.Email
+-
++  use Pleroma.Web, :mailer
+   alias Pleroma.Config
+   alias Pleroma.HTML
+-  alias Pleroma.Web.Router.Helpers
++
++  use Phoenix.VerifiedRoutes,
++    endpoint: Pleroma.Web.Endpoint,
++    router: Pleroma.Web.Router
+ 
+   defp instance_config, do: Config.get(:instance)
+   defp instance_name, do: instance_config()[:name]
+@@ -45,7 +48,7 @@ def report(to, reporter, account, statuses, comment) do
+           statuses
+           |> Enum.map(fn
+             %{id: id} ->
+-              status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
++              status_url = url(~p[/notice/#{id}])
+               "<li><a href=\"#{status_url}\">#{status_url}</li>"
+ 
+             %{"id" => id} when is_binary(id) ->
+diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
+index 1588c099c..fe319c775 100644
+--- a/lib/pleroma/emails/user_email.ex
++++ b/lib/pleroma/emails/user_email.ex
+@@ -6,12 +6,11 @@ defmodule Pleroma.Emails.UserEmail do
+   @moduledoc "User emails"
+ 
+   require Pleroma.Web.Gettext
++  use Pleroma.Web, :mailer
+ 
+   alias Pleroma.Config
+   alias Pleroma.User
+-  alias Pleroma.Web.Endpoint
+   alias Pleroma.Web.Gettext
+-  alias Pleroma.Web.Router
+ 
+   import Swoosh.Email
+   import Phoenix.Swoosh, except: [render_body: 3]
+@@ -75,7 +74,7 @@ def welcome(user, opts \\ %{}) do
+ 
+   def password_reset_email(user, token) when is_binary(token) do
+     Gettext.with_locale_or_default user.language do
+-      password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
++      password_reset_url = ~p[/api/v1/pleroma/password_reset/#{token}]
+ 
+       html_body =
+         Gettext.dpgettext(
+@@ -108,12 +107,7 @@ def user_invitation_email(
+         to_name \\ nil
+       ) do
+     Gettext.with_locale_or_default user.language do
+-      registration_url =
+-        Router.Helpers.redirect_url(
+-          Endpoint,
+-          :registration_page,
+-          user_invite_token.token
+-        )
++      registration_url = ~p[/registration/#{user_invite_token.token}]
+ 
+       html_body =
+         Gettext.dpgettext(
+@@ -146,13 +140,7 @@ def user_invitation_email(
+ 
+   def account_confirmation_email(user) do
+     Gettext.with_locale_or_default user.language do
+-      confirmation_url =
+-        Router.Helpers.confirm_email_url(
+-          Endpoint,
+-          :confirm_email,
+-          user.id,
+-          to_string(user.confirmation_token)
+-        )
++      confirmation_url = ~p[/api/account/confirm_email/#{user.id}/#{user.confirmation_token}]
+ 
+       html_body =
+         Gettext.dpgettext(
+@@ -342,7 +330,7 @@ def unsubscribe_url(user, notifications_type) do
+       |> Pleroma.JWT.generate_and_sign!()
+       |> Base.encode64()
+ 
+-    Router.Helpers.subscription_url(Endpoint, :unsubscribe, token)
++    ~p[/mailer/unsubscribe/#{token}]
+   end
+ 
+   def backup_is_ready_email(backup, admin_user_id \\ nil) do
+diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
+index 83b45e3b4..ad87837fa 100644
+--- a/lib/pleroma/user.ex
++++ b/lib/pleroma/user.ex
+@@ -44,6 +44,8 @@ defmodule Pleroma.User do
+   alias Pleroma.Web.RelMe
+   alias Pleroma.Workers.BackgroundWorker
+ 
++  use Pleroma.Web, :verified_routes
++
+   require Logger
+ 
+   @type t :: %__MODULE__{}
+@@ -2447,12 +2449,7 @@ defp validate_rel_me_field(changeset, fields, raw_fields, %User{
+           end
+ 
+         if is_url(raw_value) do
+-          frontend_url =
+-            Pleroma.Web.Router.Helpers.redirect_url(
+-              Pleroma.Web.Endpoint,
+-              :redirector_with_meta,
+-              nickname
+-            )
++          frontend_url = url(~p[/#{nickname}])
+ 
+           possible_urls = [ap_id, frontend_url]
+ 
+diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
+index ecd98b6ca..5422e7896 100644
+--- a/lib/pleroma/web.ex
++++ b/lib/pleroma/web.ex
+@@ -27,6 +27,7 @@ defmodule Pleroma.Web do
+   alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug
+   alias Pleroma.Web.Plugs.OAuthScopesPlug
+   alias Pleroma.Web.Plugs.PlugHelper
++  require Pleroma.Constants
+ 
+   def controller do
+     quote do
+@@ -37,7 +38,7 @@ def controller do
+       import Pleroma.Web.Gettext
+       import Pleroma.Web.TranslationHelpers
+ 
+-      alias Pleroma.Web.Router.Helpers, as: Routes
++      unquote(verified_routes())
+ 
+       plug(:set_put_layout)
+ 
+@@ -184,7 +185,10 @@ def view do
+ 
+       # Import convenience functions from controllers
+       import Phoenix.Controller,
+-        only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
++        only: [view_module: 1, view_template: 1]
++
++      import Phoenix.Flash
++      alias Phoenix.Flash
+ 
+       # Include shared imports and aliases for views
+       unquote(view_helpers())
+@@ -218,7 +222,7 @@ def component do
+ 
+   def router do
+     quote do
+-      use Phoenix.Router
++      use Phoenix.Router, helpers: false
+ 
+       import Plug.Conn
+       import Phoenix.Controller
+@@ -246,7 +250,24 @@ defp view_helpers do
+ 
+       import Pleroma.Web.ErrorHelpers
+       import Pleroma.Web.Gettext
+-      alias Pleroma.Web.Router.Helpers, as: Routes
++      unquote(verified_routes())
++    end
++  end
++
++  def static_paths, do: Pleroma.Constants.static_only_files()
++
++  def verified_routes do
++    quote do
++      use Phoenix.VerifiedRoutes,
++        endpoint: Pleroma.Web.Endpoint,
++        router: Pleroma.Web.Router,
++        statics: Pleroma.Web.static_paths()
++    end
++  end
++
++  def mailer do
++    quote do
++      unquote(verified_routes())
+     end
+   end
+ 
+diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
+index 649bf9095..e4c626d36 100644
+--- a/lib/pleroma/web/activity_pub/activity_pub.ex
++++ b/lib/pleroma/web/activity_pub/activity_pub.ex
+@@ -1792,6 +1792,11 @@ def pin_data_from_featured_collection(
+     end)
+   end
+ 
++  def pin_data_from_featured_collection(obj) do
++    Logger.error("Could not parse featured collection #{inspect(obj)}")
++    %{}
++  end
++
+   def fetch_and_prepare_featured_from_ap_id(nil) do
+     {:ok, %{}}
+   end
+diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
+index 6d39ad3a8..e67a14b58 100644
+--- a/lib/pleroma/web/activity_pub/builder.ex
++++ b/lib/pleroma/web/activity_pub/builder.ex
+@@ -18,6 +18,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
+   alias Pleroma.Web.CommonAPI.ActivityDraft
+   alias Pleroma.Web.Endpoint
+ 
++  use Pleroma.Web, :verified_routes
++
+   require Pleroma.Constants
+ 
+   def accept_or_reject(actor, activity, type) do
+@@ -402,6 +404,6 @@ def unpin(%User{} = user, object) do
+   end
+ 
+   defp pinned_url(nickname) when is_binary(nickname) do
+-    Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
++    url(~p[/users/#{nickname}/collections/featured])
+   end
+ end
+diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
+index 008aec475..f731b5286 100644
+--- a/lib/pleroma/web/activity_pub/utils.ex
++++ b/lib/pleroma/web/activity_pub/utils.ex
+@@ -16,10 +16,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
+   alias Pleroma.Web.ActivityPub.Visibility
+   alias Pleroma.Web.AdminAPI.AccountView
+   alias Pleroma.Web.Endpoint
+-  alias Pleroma.Web.Router.Helpers
+ 
+   import Ecto.Query
+ 
++  use Pleroma.Web, :verified_routes
++
+   require Logger
+   require Pleroma.Constants
+ 
+@@ -124,19 +125,15 @@ def make_date do
+   end
+ 
+   def generate_activity_id do
+-    generate_id("activities")
++    url(~p[/activities/#{UUID.generate()}])
+   end
+ 
+   def generate_context_id do
+-    generate_id("contexts")
++    url(~p[/contexts/#{UUID.generate()}])
+   end
+ 
+   def generate_object_id do
+-    Helpers.o_status_url(Endpoint, :object, UUID.generate())
+-  end
+-
+-  def generate_id(type) do
+-    "#{Endpoint.url()}/#{type}/#{UUID.generate()}"
++    url(~p[/objects/#{UUID.generate()}])
+   end
+ 
+   def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
+@@ -154,7 +151,7 @@ def get_notified_from_object(object) do
+     Notification.get_notified_from_activity(%Activity{data: object}, false)
+   end
+ 
+-  def maybe_create_context(context), do: context || generate_id("contexts")
++  def maybe_create_context(context), do: context || generate_context_id()
+ 
+   @doc """
+   Enqueues an activity for federation if it's local
+diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
+index 7333fb2c1..82b59c47f 100644
+--- a/lib/pleroma/web/activity_pub/views/user_view.ex
++++ b/lib/pleroma/web/activity_pub/views/user_view.ex
+@@ -12,24 +12,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do
+   alias Pleroma.Web.ActivityPub.ObjectView
+   alias Pleroma.Web.ActivityPub.Transmogrifier
+   alias Pleroma.Web.ActivityPub.Utils
+-  alias Pleroma.Web.Endpoint
+-  alias Pleroma.Web.Router.Helpers
+ 
+   require Pleroma.Web.ActivityPub.Transmogrifier
+ 
+   import Ecto.Query
+ 
+   def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
+-    %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
++    %{"sharedInbox" => ~p"/inbox"}
+   end
+ 
+   def render("endpoints.json", %{user: %User{local: true} = _user}) do
+     %{
+-      "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
+-      "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
+-      "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
+-      "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
+-      "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
++      "oauthAuthorizationEndpoint" => ~p"/oauth/authorize",
++      "oauthRegistrationEndpoint" => ~p"/api/v1/apps",
++      "oauthTokenEndpoint" => ~p"/oauth/token",
++      "sharedInbox" => ~p"/inbox",
++      "uploadMedia" => ~p"/api/ap/upload_media"
+     }
+   end
+ 
+diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+index 1d7ac78a0..7344e1f77 100644
+--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
++++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+@@ -17,9 +17,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
+   alias Pleroma.Web.AdminAPI
+   alias Pleroma.Web.AdminAPI.AccountView
+   alias Pleroma.Web.AdminAPI.ModerationLogView
+-  alias Pleroma.Web.Endpoint
+   alias Pleroma.Web.Plugs.OAuthScopesPlug
+-  alias Pleroma.Web.Router
+ 
+   @users_page_size 50
+ 
+@@ -256,7 +254,7 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
+     conn
+     |> json(%{
+       token: token.token,
+-      link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
++      link: url(~p[/api/v1/pleroma/password_reset/#{token.token}])
+     })
+   end
+ 
+diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
+index e3a251ca1..64593767d 100644
+--- a/lib/pleroma/web/endpoint.ex
++++ b/lib/pleroma/web/endpoint.ex
+@@ -97,7 +97,7 @@ defmodule Pleroma.Web.Endpoint do
+     Plug.Static,
+     at: "/",
+     from: :pleroma,
+-    only: Pleroma.Constants.static_only_files(),
++    only: Pleroma.Web.static_paths(),
+     # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
+     gzip: true,
+     cache_control_for_etags: @static_cache_control,
+diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
+index dc3b1f94b..b320c9224 100644
+--- a/lib/pleroma/web/feed/user_controller.ex
++++ b/lib/pleroma/web/feed/user_controller.ex
+@@ -30,7 +30,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params)
+ 
+   def feed_redirect(conn, %{"nickname" => nickname}) do
+     with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+-      redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom")
++      redirect(conn, external: "#{url(~p"/users/#{user.nickname}/feed")}.atom")
+     end
+   end
+ 
+diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
+index 7b6e01aad..b24f00620 100644
+--- a/lib/pleroma/web/masto_fe_controller.ex
++++ b/lib/pleroma/web/masto_fe_controller.ex
+@@ -34,9 +34,9 @@ def index(conn, _params) do
+ 
+       index =
+         if flavour == "fedibird-fe" do
+-          "fedibird.index.html"
++          "fedibird.html"
+         else
+-          "glitchsoc.index.html"
++          "glitchsoc.html"
+         end
+ 
+       conn
+diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+index a9ccaa982..30e40ac42 100644
+--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
++++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+@@ -54,12 +54,7 @@ def login(conn, params) do
+   defp redirect_to_oauth_form(conn, _params) do
+     with {:ok, app} <- local_mastofe_app() do
+       path =
+-        Routes.o_auth_path(conn, :authorize,
+-          response_type: "code",
+-          client_id: app.client_id,
+-          redirect_uri: ".",
+-          scope: Enum.join(app.scopes, " ")
+-        )
++        ~p[/oauth/authorize?#{[response_type: "code", client_id: app.client_id, redirect_uri: ".", scope: Enum.join(app.scopes, " ")]}]
+ 
+       redirect(conn, to: path)
+     end
+@@ -91,7 +86,7 @@ def password_reset(conn, params) do
+   defp local_mastodon_post_login_path(conn) do
+     case get_session(conn, :return_to) do
+       nil ->
+-        Routes.masto_fe_path(conn, :index, ["getting-started"])
++        ~p"/web/getting-started"
+ 
+       return_to ->
+         delete_session(conn, :return_to)
+diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
+index 47d1616c4..ac0955534 100644
+--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
++++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
+@@ -322,7 +322,7 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
+ 
+       url =
+         if user.local do
+-          Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
++          url(~p[/notice/#{activity}])
+         else
+           object.data["url"] || object.data["external_url"] || object.data["id"]
+         end
+diff --git a/lib/pleroma/web/mastodon_api/views/tag_view.ex b/lib/pleroma/web/mastodon_api/views/tag_view.ex
+index 02108c736..6d3ea3c1a 100644
+--- a/lib/pleroma/web/mastodon_api/views/tag_view.ex
++++ b/lib/pleroma/web/mastodon_api/views/tag_view.ex
+@@ -1,7 +1,6 @@
+ defmodule Pleroma.Web.MastodonAPI.TagView do
+   use Pleroma.Web, :view
+   alias Pleroma.User
+-  alias Pleroma.Web.Router.Helpers
+ 
+   def render("index.json", %{tags: tags, for_user: user}) do
+     render_many(tags, __MODULE__, "show.json", %{for_user: user})
+@@ -17,7 +16,7 @@ def render("show.json", %{tag: tag, for_user: user}) do
+ 
+     %{
+       name: tag.name,
+-      url: Helpers.tag_feed_url(Pleroma.Web.Endpoint, :feed, tag.name),
++      url: url(~p[/tags/#{tag.name}]),
+       history: [],
+       following: following
+     }
+diff --git a/lib/pleroma/web/metadata/providers/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex
+index d0ab5c19e..15f47b843 100644
+--- a/lib/pleroma/web/metadata/providers/feed.ex
++++ b/lib/pleroma/web/metadata/providers/feed.ex
+@@ -3,9 +3,9 @@
+ # SPDX-License-Identifier: AGPL-3.0-only
+ 
+ defmodule Pleroma.Web.Metadata.Providers.Feed do
+-  alias Pleroma.Web.Endpoint
+   alias Pleroma.Web.Metadata.Providers.Provider
+-  alias Pleroma.Web.Router.Helpers
++
++  use Pleroma.Web, :verified_routes
+ 
+   @behaviour Provider
+ 
+@@ -16,7 +16,7 @@ def build_tags(%{user: user}) do
+        [
+          rel: "alternate",
+          type: "application/atom+xml",
+-         href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom"
++         href: ~p[/users/#{user.nickname}/feed.atom]
+        ], []}
+     ]
+   end
+diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
+index b2497d14e..ab48ea272 100644
+--- a/lib/pleroma/web/metadata/providers/twitter_card.ex
++++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
+@@ -10,6 +10,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
+   alias Pleroma.Web.Metadata.Providers.Provider
+   alias Pleroma.Web.Metadata.Utils
+ 
++  use Pleroma.Web, :verified_routes
++
+   @behaviour Provider
+   @media_types ["image", "audio", "video"]
+ 
+@@ -112,7 +114,7 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
+   defp build_attachments(_id, _object), do: []
+ 
+   defp player_url(id) do
+-    Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
++    url(~p[/notice/#{id}/embed_player])
+   end
+ 
+   # Videos have problems without dimensions, but we used to not provide WxH for images.
+diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
+index 277df1c46..ba33dc9e7 100644
+--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
++++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
+@@ -449,7 +449,7 @@ def prepare_request(%Plug.Conn{} = conn, %{
+       |> Map.put("state", state)
+ 
+     # Handing the request to Ueberauth
+-    redirect(conn, to: Routes.o_auth_path(conn, :request, provider, params))
++    redirect(conn, to: ~p"/oauth/#{provider}?#{params}")
+   end
+ 
+   def request(%Plug.Conn{} = conn, params) do
+@@ -623,7 +623,7 @@ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested
+   end
+ 
+   # Special case: Local MastodonFE
+-  defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login)
++  defp redirect_uri(_, "."), do: url(~p"/web/login")
+ 
+   defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
+ 
+diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
+index 95a22895e..2b2872c9a 100644
+--- a/lib/pleroma/web/o_status/o_status_controller.ex
++++ b/lib/pleroma/web/o_status/o_status_controller.ex
+@@ -14,7 +14,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
+   alias Pleroma.Web.Fallback.RedirectController
+   alias Pleroma.Web.Metadata.PlayerView
+   alias Pleroma.Web.Plugs.RateLimiter
+-  alias Pleroma.Web.Router
+ 
+   plug(
+     RateLimiter,
+@@ -87,7 +86,7 @@ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
+             %{
+               activity_id: activity.id,
+               object: object,
+-              url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
++              url: url(~p[/notice/#{activity.id}]),
+               user: user
+             }
+           )
+diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
+index 488108b08..eb6a46736 100644
+--- a/lib/pleroma/web/plugs/http_signature_plug.ex
++++ b/lib/pleroma/web/plugs/http_signature_plug.ex
+@@ -5,8 +5,9 @@
+ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
+   import Plug.Conn
+   import Phoenix.Controller, only: [get_format: 1]
++
++  use Pleroma.Web, :verified_routes
+   alias Pleroma.Activity
+-  alias Pleroma.Web.Router
+   alias Pleroma.Signature
+   alias Pleroma.Instances
+   require Logger
+@@ -32,10 +33,10 @@ def call(conn, _opts) do
+   end
+ 
+   def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do
+-    ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id)
++    ap_id = url(~p[/objects/#{id}])
+ 
+     with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
+-      ["/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
++      [~p"/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
+     else
+       _ -> []
+     end
+diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
+index 56ee4e41e..f0d45293e 100644
+--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
++++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
+@@ -11,7 +11,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
+   alias Pleroma.Web.ActivityPub.ActivityPub
+   alias Pleroma.Web.ActivityPub.Visibility
+   alias Pleroma.Web.Metadata
+-  alias Pleroma.Web.Router.Helpers
+ 
+   plug(:put_layout, :static_fe)
+   plug(:assign_id)
+@@ -111,11 +110,11 @@ def show(%{assigns: %{username_or_id: username_or_id, tab: tab}} = conn, params)
+   end
+ 
+   def show(%{assigns: %{object_id: _}} = conn, _params) do
+-    url = Helpers.url(conn) <> conn.request_path
++    url = unverified_url(conn, conn.request_path)
+ 
+     case Activity.get_create_by_object_ap_id_with_object(url) do
+       %Activity{} = activity ->
+-        to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
++        to = ~p[/notice/#{activity}]
+         redirect(conn, to: to)
+ 
+       _ ->
+@@ -124,11 +123,11 @@ def show(%{assigns: %{object_id: _}} = conn, _params) do
+   end
+ 
+   def show(%{assigns: %{activity_id: _}} = conn, _params) do
+-    url = Helpers.url(conn) <> conn.request_path
++    url = unverified_url(conn, conn.request_path)
+ 
+     case Activity.get_by_ap_id(url) do
+       %Activity{} = activity ->
+-        to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity)
++        to = ~p[/notice/#{activity}]
+         redirect(conn, to: to)
+ 
+       _ ->
+@@ -167,7 +166,7 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
+ 
+     link =
+       case user.local do
+-        true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
++        true -> ~p[/notice/#{activity}]
+         _ -> data["url"] || data["external_url"] || data["id"]
+       end
+ 
+diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/static_fe/static_fe_view.ex
+index f0c9ddd22..c1d83c5a0 100644
+--- a/lib/pleroma/web/static_fe/static_fe_view.ex
++++ b/lib/pleroma/web/static_fe/static_fe_view.ex
+@@ -11,7 +11,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEView do
+   alias Pleroma.Web.Gettext
+   alias Pleroma.Web.MediaProxy
+   alias Pleroma.Web.Metadata.Utils
+-  alias Pleroma.Web.Router.Helpers
+ 
+   use Phoenix.HTML
+ 
+diff --git a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
+index a0b0a2361..010a7fbad 100644
+--- a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
++++ b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex
+@@ -2,7 +2,7 @@
+ 
+ <h3>After you submit, you will need to refresh manually to get your new frontend!</h3>
+ 
+-<%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %>
++<%= form_for @conn, ~p"/akkoma/frontend", fn f -> %>
+   <%= select(f, :frontend, @choices) %>
+ 
+   <%= submit do: "submit" %>
+diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+index 6d497e84c..e85c08b2b 100644
+--- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex
++++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex
+@@ -9,13 +9,13 @@
+       xmlns:ostatus="http://ostatus.org/schema/1.0"
+       xmlns:statusnet="http://status.net/schema/api/1/">
+ 
+-    <id><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
++    <id><%= '#{url(~p"/tags/#{@tag}")}.rss' %></id>
+     <title>#<%= @tag %></title>
+ 
+     <subtitle><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></subtitle>
+     <logo><%= feed_logo() %></logo>
+     <updated><%= most_recent_update(@activities) %></updated>
+-    <link rel="self" href="<%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.atom'  %>" type="application/atom+xml"/>
++    <link rel="self" href="<%= '#{url(~p"/tags/#{@tag}")}.atom'  %>" type="application/atom+xml"/>
+     <%= for activity <- @activities do %>
+     <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
+     <% end %>
+diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+index edcc3e436..7ee3ba5a3 100644
+--- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex
++++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex
+@@ -5,7 +5,7 @@
+ 
+     <title>#<%= @tag %></title>
+     <description><%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %></description>
+-    <link><%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
++    <link><%= '#{url(~p"/tags/#{@tag}")}.rss' %></link>
+     <webfeeds:logo><%= feed_logo() %></webfeeds:logo>
+     <webfeeds:accentColor>2b90d9</webfeeds:accentColor>
+     <%= for activity <- @activities do %>
+diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex
+index 5c1f0ecbc..03585a9d5 100644
+--- a/lib/pleroma/web/templates/feed/feed/user.atom.eex
++++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex
+@@ -6,16 +6,16 @@
+   xmlns:poco="http://portablecontacts.net/spec/1.0"
+   xmlns:ostatus="http://ostatus.org/schema/1.0">
+ 
+-  <id><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
++  <id><%= url(~p"/users/#{@user.nickname}/feed") <> ".atom" %></id>
+   <title><%= @user.nickname <> "'s timeline" %></title>
+   <updated><%= most_recent_update(@activities, @user) %></updated>
+   <logo><%= logo(@user) %></logo>
+-  <link rel="self" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
++  <link rel="self" href="<%= '#{url(~p"/users/#{@user.nickname}/feed")}.atom' %>" type="application/atom+xml"/>
+ 
+   <%= render @view_module, "_author.atom", assigns %>
+ 
+   <%= if last_activity(@activities) do %>
+-    <link rel="next" href="<%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
++    <link rel="next" href="<%= '#{url(~p"/users/#{@user.nickname}/feed")}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
+   <% end %>
+ 
+   <%= for activity <- @activities do %>
+diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex
+index 6b842a085..f2eb7337e 100644
+--- a/lib/pleroma/web/templates/feed/feed/user.rss.eex
++++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex
+@@ -1,16 +1,16 @@
+ <?xml version="1.0" encoding="UTF-8" ?>
+ <rss version="2.0">
+   <channel>
+-    <guid><%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %></guid>
++    <guid><%= url(~p"/users/#{@user.nickname}/feed") <> ".rss" %></guid>
+     <title><%= @user.nickname <> "'s timeline" %></title>
+     <updated><%= most_recent_update(@activities, @user) %></updated>
+     <image><%= logo(@user) %></image>
+-    <link><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %></link>
++    <link><%= '#{url(~p"/users/#{@user.nickname}/feed")}.rss' %></link>
+ 
+     <%= render @view_module, "_author.rss", assigns %>
+ 
+     <%= if last_activity(@activities) do %>
+-      <link rel="next"><%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %></link>
++      <link rel="next"><%= '#{url(~p"/users/#{@user.nickname}/feed")}.rss?max_id=#{last_activity(@activities).id}' %></link>
+     <% end %>
+ 
+     <%= for activity <- @activities do %>
+diff --git a/lib/pleroma/web/templates/masto_fe/fedibird.html.heex b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex
+new file mode 100644
+index 000000000..7070bd8d8
+--- /dev/null
++++ b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex
+@@ -0,0 +1,58 @@
++<!DOCTYPE html>
++<!-- FEDIBIRD -->
++<html lang="en">
++  <head>
++    <meta charset="utf-8" />
++    <meta content="width=device-width, initial-scale=1" name="viewport" />
++    <title>
++      <%= Config.get([:instance, :name]) %>
++    </title>
++    <link rel="icon" type="image/png" href="/favicon.png" />
++    <link rel="manifest" type="applicaton/manifest+json" {%{href: ~p"/web/manifest.json"}} />
++
++    <meta name="theme-color" {%{content: Config.get([:manifest, :theme_color])}} />
++
++    <script id="initial-state" type="application/json">
++      <%= initial_state(@token, @user, @custom_emojis) %>
++    </script>
++
++    <script crossorigin="anonymous" src="/packs/js/common.js">
++    </script>
++    <script crossorigin="anonymous" src="/packs/js/locale_en.js">
++    </script>
++
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/getting_started.js"
++    />
++    <link rel="preload" as="script" crossorigin="anonymous" href="/packs/js/features/compose.js" />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/home_timeline.js"
++    />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/public_timeline.js"
++    />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/notifications.js"
++    />
++    <script crossorigin="anonymous" src="/packs/js/application.js">
++    </script>
++
++    <link rel="stylesheet" media="all" href="/packs/css/common.css" />
++    <link rel="stylesheet" media="all" href="/packs/css/default.css" />
++  </head>
++  <body class="app-body no-reduce-motion system-font">
++    <div class="app-holder" data-props="{&quot;locale&quot;:&quot;en&quot;}" id="mastodon"></div>
++  </body>
++</html>
+diff --git a/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex b/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
+deleted file mode 100644
+index 6730c0ecc..000000000
+--- a/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex
++++ /dev/null
+@@ -1,35 +0,0 @@
+-<!DOCTYPE html>
+-<html lang='en'>
+-<head>
+-<meta charset='utf-8'>
+-<meta content='width=device-width, initial-scale=1' name='viewport'>
+-<title>
+-<%= Config.get([:instance, :name]) %>
+-</title>
+-<link rel="icon" type="image/png" href="/favicon.png"/>
+-<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+-
+-<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
+-
+-<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
+-
+-<script crossorigin='anonymous' src="/packs/js/common.js"></script>
+-<script crossorigin='anonymous' src="/packs/js/locale_en.js"></script>
+-
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/getting_started.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/compose.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/home_timeline.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/public_timeline.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/notifications.js'>
+-<script crossorigin='anonymous' src="/packs/js/application.js"></script>
+-
+-
+-<link rel="stylesheet" media="all" href="/packs/css/common.css" />
+-<link rel="stylesheet" media="all" href="/packs/css/default.css" />
+-
+-</head>
+-<body class='app-body no-reduce-motion system-font'>
+-  <div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
+-  </div>
+-</body>
+-</html>
+diff --git a/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
+new file mode 100644
+index 000000000..469c201a5
+--- /dev/null
++++ b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
+@@ -0,0 +1,57 @@
++<!DOCTYPE html>
++<!-- GLITCHSOC -->
++<html lang="en">
++  <head>
++    <meta charset="utf-8" />
++    <meta content="width=device-width, initial-scale=1" name="viewport" />
++    <title>
++      <%= Config.get([:instance, :name]) %>
++    </title>
++    <link rel="icon" type="image/png" href="/favicon.png" />
++    <link rel="manifest" type="applicaton/manifest+json" {%{href: ~p"/web/manifest.json"}} />
++
++    <meta name="theme-color" {%{content: Config.get([:manifest, :theme_color])}} />
++
++    <script crossorigin="anonymous" src="/packs/js/locales.js">
++    </script>
++    <script crossorigin="anonymous" src="/packs/js/locales/glitch/en.js">
++    </script>
++
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/getting_started.js"
++    />
++    <link rel="preload" as="script" crossorigin="anonymous" href="/packs/js/features/compose.js" />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/home_timeline.js"
++    />
++    <link
++      rel="preload"
++      as="script"
++      crossorigin="anonymous"
++      href="/packs/js/features/notifications.js"
++    />
++    <script id="initial-state" type="application/json">
++      <%= initial_state(@token, @user, @custom_emojis) %>
++    </script>
++
++    <script src="/packs/js/core/common.js">
++    </script>
++    <link rel="stylesheet" media="all" href="/packs/css/core/common.css" />
++
++    <script src="/packs/js/flavours/glitch/common.js">
++    </script>
++    <link rel="stylesheet" media="all" href="/packs/css/flavours/glitch/common.css" />
++
++    <script src="/packs/js/flavours/glitch/home.js">
++    </script>
++  </head>
++  <body class="app-body no-reduce-motion system-font">
++    <div class="app-holder" data-props="{&quot;locale&quot;:&quot;en&quot;}" id="mastodon"></div>
++  </body>
++</html>
+diff --git a/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex b/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex
+deleted file mode 100644
+index dadf8f413..000000000
+--- a/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex
++++ /dev/null
+@@ -1,35 +0,0 @@
+-<!DOCTYPE html>
+-<html lang='en'>
+-<head>
+-<meta charset='utf-8'>
+-<meta content='width=device-width, initial-scale=1' name='viewport'>
+-<title>
+-<%= Config.get([:instance, :name]) %>
+-</title>
+-<link rel="icon" type="image/png" href="/favicon.png"/>
+-<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
+-
+-<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
+-
+-<script crossorigin='anonymous' src="/packs/js/locales.js"></script>
+-<script crossorigin='anonymous' src="/packs/js/locales/glitch/en.js"></script>
+-
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/getting_started.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/compose.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/home_timeline.js'>
+-<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/notifications.js'>
+-<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
+-
+-<script src="/packs/js/core/common.js"></script>
+-<link rel="stylesheet" media="all" href="/packs/css/core/common.css" />
+-
+-<script src="/packs/js/flavours/glitch/common.js"></script>
+-<link rel="stylesheet" media="all" href="/packs/css/flavours/glitch/common.css" />
+-
+-<script src="/packs/js/flavours/glitch/home.js"></script>
+-</head>
+-<body class='app-body no-reduce-motion system-font'>
+-  <div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
+-  </div>
+-</body>
+-</html>
+diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+index ee40cf277..b9b08c45d 100644
+--- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
++++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex
+@@ -1,15 +1,15 @@
+ <div>
+-  <%= if get_flash(@conn, :info) do %>
+-  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++  <%= if Flash.get(@flash, :info) do %>
++  <p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+   <% end %>
+-  <%= if get_flash(@conn, :error) do %>
+-  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++  <%= if Flash.get(@flash, :error) do %>
++  <p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+   <% end %>
+   <div class="panel-heading">
+       <%= Gettext.dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %>
+   </div>
+   <div class="panel-content">
+-      <%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
++      <%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
+       <div class="input">
+         <%= label f, :code, Gettext.dpgettext("static_pages", "mfa recover recovery code prompt", "Recovery code") %>
+         <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %>
+@@ -21,7 +21,7 @@
+ 
+       <%= submit Gettext.dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %>
+       <% end %>
+-      <a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
++      <a href="<%= ~p"/oauth/mfa?#{[challenge_type: "totp", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri]}" %>">
+         <%= Gettext.dpgettext("static_pages", "mfa recover use 2fa code link", "Enter a two-factor code") %>
+       </a>
+ 
+diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+index 734e62112..59827780b 100644
+--- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
++++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex
+@@ -1,15 +1,15 @@
+ <div>
+-  <%= if get_flash(@conn, :info) do %>
+-  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++  <%= if Flash.get(@flash, :info) do %>
++  <p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+   <% end %>
+-  <%= if get_flash(@conn, :error) do %>
+-  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++  <%= if Flash.get(@flash, :error) do %>
++  <p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+   <% end %>
+   <div class="panel-heading">
+       <%= Gettext.dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %>
+   </div>
+   <div class="panel-content">
+-      <%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %>
++      <%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
+       <div class="input">
+         <%= label f, :code, Gettext.dpgettext("static_pages", "mfa auth code prompt", "Authentication code") %>
+         <%= text_input f, :code, [autocomplete: "one-time-code", autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %>
+@@ -21,7 +21,7 @@
+ 
+       <%= submit Gettext.dpgettext("static_pages", "mfa auth verify code button", "Verify") %>
+       <% end %>
+-      <a href="<%= Routes.mfa_path(@conn, :show, %{challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri}) %>">
++      <a href="<%= ~p"/oauth/mfa?#{[challenge_type: "recovery", mfa_token: @mfa_token, state: @state, redirect_uri: @redirect_uri]}" %>">
+         <%= Gettext.dpgettext("static_pages", "mfa auth page use recovery code link", "Enter a two-factor recovery code") %>
+       </a>
+   </div>
+diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+index 8b894cd58..97f2b770f 100644
+--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
++++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+@@ -1,6 +1,6 @@
+ <h2><%= Gettext.dpgettext("static_pages", "oauth external provider page title", "Sign in with external provider") %></h2>
+ 
+-<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
++<%= form_for @conn, ~p"/oauth/prepare_request", [as: "authorization", method: "get"], fn f -> %>
+   <div style="display: none">
+     <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+   </div>
+diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+index 1f661efb2..601b16b98 100644
+--- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
++++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
+@@ -1,14 +1,14 @@
+-<%= if get_flash(@conn, :info) do %>
+-  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++<%= if Flash.get(@flash, :info) do %>
++  <p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+ <% end %>
+-<%= if get_flash(@conn, :error) do %>
+-  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++<%= if Flash.get(@flash, :error) do %>
++  <p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+ <% end %>
+ 
+ <h2><%= Gettext.dpgettext("static_pages", "oauth register page title", "Registration Details") %></h2>
+ 
+ <p><%= Gettext.dpgettext("static_pages", "oauth register page fill form prompt", "If you'd like to register a new account, please provide the details below.") %></p>
+-<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>
++<%= form_for @conn, ~p"/oauth/register", [as: "authorization"], fn f -> %>
+ 
+ <div class="input">
+   <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register page nickname prompt", "Nickname") %>
+diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+index 986e6ffce..420a17562 100644
+--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
++++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+@@ -1,11 +1,11 @@
+-<%= if get_flash(@conn, :info) do %>
+-<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
++<%= if Flash.get(@flash, :info) do %>
++<p class="alert alert-info" role="alert"><%= Flash.get(@flash, :info) %></p>
+ <% end %>
+-<%= if get_flash(@conn, :error) do %>
+-<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
++<%= if Flash.get(@flash, :error) do %>
++<p class="alert alert-danger" role="alert"><%= Flash.get(@flash, :error) %></p>
+ <% end %>
+ 
+-<%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
++<%= form_for @conn, ~p"/oauth/authorize", [as: "authorization"], fn f -> %>
+ 
+ <%= if @user do %>
+   <div class="account-header">
+diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+index 3d1cf77e5..0bc44738b 100644
+--- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
++++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex
+@@ -36,7 +36,7 @@
+         </div>
+       </div>
+       <div class="remote-follow">
+-        <form method="POST" action="<%= Helpers.util_path(@conn, :remote_subscribe) %>">
++        <form method="POST" action="<%= ~p"/main/ostatus" %>">
+           <input type="hidden" name="nickname" value="<%= @user.nickname %>">
+           <input type="hidden" name="profile" value="">
+           <button type="submit" class="button-default"><%= Gettext.dpgettext("static_pages", "static fe profile page remote follow button", "Remote follow") %></button>
+diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
+index 6a544af51..390a8371f 100644
+--- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
+@@ -1,5 +1,5 @@
+ <h2>Password Reset for <%= @user.nickname %></h2>
+-<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
++<%= form_for @conn, ~p"/api/v1/pleroma/password_reset", [as: "data"], fn f -> %>
+   <div class="form-row">
+     <%= label f, :password, Gettext.dpgettext("static_pages", "password reset form password prompt", "Password") %>
+     <%= password_input f, :password %>
+diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
+index e2d251fac..894b5c6ee 100644
+--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
+@@ -4,7 +4,7 @@
+     <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %></h2>
+     <img height="128" width="128" src="<%= avatar_url(@followee) %>">
+     <p><%= @followee.nickname %></p>
+-    <%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
++    <%= form_for @conn, ~p"/ostatus_subscribe", [as: "user"], fn f -> %>
+     <%= hidden_input f, :id, value: @followee.id %>
+     <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %>
+     <% end %>
+diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+index 26340a906..b0084aac4 100644
+--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
+@@ -4,7 +4,7 @@
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %></h2>
+ <p><%= @followee.nickname %></p>
+ <img height="128" width="128" src="<%= avatar_url(@followee) %>">
+-<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
++<%= form_for @conn, ~p"/ostatus_subscribe", [as: "authorization"], fn f -> %>
+ <%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
+ <br>
+ <%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %>
+diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
+index 638212c1e..f34eba165 100644
+--- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex
+@@ -4,7 +4,7 @@
+ <h2><%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %></h2>
+ <p><%= @followee.nickname %></p>
+ <img height="128" width="128" src="<%= avatar_url(@followee) %>">
+-<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
++<%= form_for @conn, ~p"/ostatus_subscribe", [as: "mfa"], fn f -> %>
+ <%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
+ <br>
+ <%= hidden_input f, :id, value: @followee.id %>
+diff --git a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
+index d77174967..3105772e2 100644
+--- a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex
+@@ -2,7 +2,7 @@
+   <h2><%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %></h2>
+ <% else %>
+   <h2><%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %></h2>
+-  <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "status"], fn f -> %>
++  <%= form_for @conn, ~p"/main/ostatus", [as: "status"], fn f -> %>
+   <%= hidden_input f, :status_id, value: @status_id %>
+   <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
+   <%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %>
+diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
+index 848660f26..47b73f676 100644
+--- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
++++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex
+@@ -2,7 +2,7 @@
+   <h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %></h2>
+ <% else %>
+   <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %></h2>
+-  <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
++  <%= form_for @conn, ~p"/main/ostatus", [as: "user"], fn f -> %>
+   <%= hidden_input f, :nickname, value: @nickname %>
+   <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
+   <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %>
+diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+index 42d7601ed..1927d2021 100644
+--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
++++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+@@ -38,7 +38,7 @@ def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
+   defp follow_status(conn, _user, acct) do
+     with {:ok, object} <- Fetcher.fetch_object_from_id(acct),
+          %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do
+-      redirect(conn, to: Routes.o_status_path(conn, :notice, activity_id))
++      redirect(conn, to: ~p"/notice/#{activity_id}")
+     else
+       error ->
+         handle_follow_error(conn, error)
+diff --git a/lib/pleroma/web/views/embed_view.ex b/lib/pleroma/web/views/embed_view.ex
+index 913d717be..fe1009e2f 100644
+--- a/lib/pleroma/web/views/embed_view.ex
++++ b/lib/pleroma/web/views/embed_view.ex
+@@ -13,7 +13,6 @@ defmodule Pleroma.Web.EmbedView do
+   alias Pleroma.Web.Gettext
+   alias Pleroma.Web.MediaProxy
+   alias Pleroma.Web.Metadata.Utils
+-  alias Pleroma.Web.Router.Helpers
+ 
+   import Phoenix.HTML
+ 
+@@ -48,7 +47,7 @@ defp activity_content(%Activity{object: %Object{data: %{"content" => content}}})
+   defp activity_content(_), do: nil
+ 
+   defp activity_url(%User{local: true}, activity) do
+-    Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
++    ~p[/notice/#{activity}]
+   end
+ 
+   defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) do
+diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
+index 305368c9d..dfb235976 100644
+--- a/lib/pleroma/web/views/masto_fe_view.ex
++++ b/lib/pleroma/web/views/masto_fe_view.ex
+@@ -85,7 +85,7 @@ def render("manifest.json", _params) do
+       background_color: Config.get([:manifest, :background_color]),
+       display: "standalone",
+       scope: Pleroma.Web.Endpoint.url(),
+-      start_url: Routes.masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
++      start_url: ~p"/web/getting-started",
+       categories: [
+         "social"
+       ],
+diff --git a/mix.exs b/mix.exs
+index 235a3d65d..01a6e8a82 100644
+--- a/mix.exs
++++ b/mix.exs
+@@ -7,7 +7,7 @@ def project do
+       version: version("3.10.4"),
+       elixir: "~> 1.14",
+       elixirc_paths: elixirc_paths(Mix.env()),
+-      compilers: [:phoenix] ++ Mix.compilers(),
++      compilers: Mix.compilers(),
+       elixirc_options: [warnings_as_errors: warnings_as_errors()],
+       xref: [exclude: [:eldap]],
+       start_permanent: Mix.env() == :prod,
+@@ -114,7 +114,9 @@ defp oauth_deps do
+   # Type `mix help deps` for examples and options.
+   defp deps do
+     [
+-      {:phoenix, "~> 1.6.15"},
++      {:phoenix, "~> 1.7.0"},
++      {:phoenix_view, "~> 2.0"},
++      {:phoenix_live_dashboard, "~> 0.7.2"},
+       {:tzdata, "~> 1.1.1"},
+       {:plug_cowboy, "~> 2.6"},
+       {:phoenix_pubsub, "~> 2.1"},
+@@ -184,7 +186,6 @@ defp deps do
+        git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git",
+        ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"},
+       {:nimble_parsec, "~> 1.3", override: true},
+-      {:phoenix_live_dashboard, "~> 0.7.2"},
+       {:ecto_psql_extras, "~> 0.7"},
+       {:elasticsearch,
+        git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
+diff --git a/mix.lock b/mix.lock
+index 19b954d81..3817ec210 100644
+--- a/mix.lock
++++ b/mix.lock
+@@ -38,7 +38,7 @@
+   "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"},
+   "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
+   "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
+-  "ex_doc": {:hex, :ex_doc, "0.30.3", "bfca4d340e3b95f2eb26e72e4890da83e2b3a5c5b0e52607333bf5017284b063", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "fbc8702046c1d25edf79de376297e608ac78cdc3a29f075484773ad1718918b6"},
++  "ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"},
+   "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
+   "ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
+   "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
+@@ -63,7 +63,7 @@
+   "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
+   "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
+   "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []},
+-  "mail": {:hex, :mail, "0.3.0", "f353ef5f41d9f2e483ba7c5df92cdfe27b990b2d8c8c41d84c7b2b40ec33989f", [:mix], [], "hexpm", "523d700b2231d887dc4ee41d3bb13f48358f6f118e55a67cef6d630d7907116c"},
++  "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
+   "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
+   "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_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
+@@ -83,9 +83,9 @@
+   "oban": {:hex, :oban, "2.15.2", "8f934a49db39163633965139c8846d8e24c2beb4180f34a005c2c7c3f69a6aa2", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0f4a579ea48fc7489e0d84facf8b01566e142bdc6542d7dabce32c10e664f1e9"},
+   "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"},
+   "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+-  "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [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]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"},
++  "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [: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.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"},
+   "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
+-  "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
++  "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"},
+   "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
+   "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, 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]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
+   "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
+@@ -108,7 +108,7 @@
+   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
+   "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
+   "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
+-  "swoosh": {:hex, :swoosh, "1.11.4", "9b353f998cba3c5e101a0669559c2fb2757b5d9eb7db058bf08687d82e93e416", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d3390914022a456ae1604bfcb3431bd12509b2afe8c70296bae6c9dca4903d0f"},
++  "swoosh": {:hex, :swoosh, "1.11.5", "429dccde78e2f60c6339e96917efecebca9d1f254d2878a150f580d2f782260b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21ee57dcd68d2f56d3bbe11e76d56d142b221bb12b6018c551cc68442b800040"},
+   "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
+   "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
+   "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
+@@ -126,5 +126,7 @@
+   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
+   "vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"},
+   "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
++  "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"},
++  "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"},
+   "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
+ }
+diff --git a/test/pleroma/emails/admin_email_test.exs b/test/pleroma/emails/admin_email_test.exs
+index e65752c23..6b0acf817 100644
+--- a/test/pleroma/emails/admin_email_test.exs
++++ b/test/pleroma/emails/admin_email_test.exs
+@@ -7,7 +7,6 @@ defmodule Pleroma.Emails.AdminEmailTest do
+   import Pleroma.Factory
+ 
+   alias Pleroma.Emails.AdminEmail
+-  alias Pleroma.Web.Router.Helpers
+ 
+   test "build report email" do
+     config = Pleroma.Config.get(:instance)
+@@ -18,7 +17,7 @@ test "build report email" do
+     res =
+       AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
+ 
+-    status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
++    status_url = url(~p[/notice/12])
+     reporter_url = reporter.ap_id
+     account_url = account.ap_id
+ 
+diff --git a/test/pleroma/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs
+index 564552004..259efbb65 100644
+--- a/test/pleroma/emails/user_email_test.exs
++++ b/test/pleroma/emails/user_email_test.exs
+@@ -6,8 +6,6 @@ defmodule Pleroma.Emails.UserEmailTest do
+   use Pleroma.DataCase, async: true
+ 
+   alias Pleroma.Emails.UserEmail
+-  alias Pleroma.Web.Endpoint
+-  alias Pleroma.Web.Router
+ 
+   import Pleroma.Factory
+ 
+@@ -18,7 +16,7 @@ test "build password reset email" do
+     assert email.from == {config[:name], config[:notify_email]}
+     assert email.to == [{user.name, user.email}]
+     assert email.subject == "Password reset"
+-    assert email.html_body =~ Router.Helpers.reset_password_url(Endpoint, :reset, "test_token")
++    assert email.html_body =~ ~p"/api/v1/pleroma/password_reset/test_token"
+   end
+ 
+   test "build user invitation email" do
+@@ -30,8 +28,7 @@ test "build user invitation email" do
+     assert email.subject == "Invitation to Akkoma"
+     assert email.to == [{"Jonh", "test@test.com"}]
+ 
+-    assert email.html_body =~
+-             Router.Helpers.redirect_url(Endpoint, :registration_page, token.token)
++    assert email.html_body =~ ~p[/registration/#{token.token}]
+   end
+ 
+   test "build account confirmation email" do
+@@ -42,8 +39,7 @@ test "build account confirmation email" do
+     assert email.to == [{user.name, user.email}]
+     assert email.subject == "#{config[:name]} account confirmation"
+ 
+-    assert email.html_body =~
+-             Router.Helpers.confirm_email_url(Endpoint, :confirm_email, user.id, "conf-token")
++    assert email.html_body =~ ~p[/account/confirm_email/#{user.id}/conf-token]
+   end
+ 
+   test "build approval pending email" do
+diff --git a/test/pleroma/integration/federation_test.exs b/test/pleroma/integration/federation_test.exs
+index da433e2c0..e4e37aa9f 100644
+--- a/test/pleroma/integration/federation_test.exs
++++ b/test/pleroma/integration/federation_test.exs
+@@ -31,10 +31,9 @@ test "within/2 captures local bindings and executes block on remote node" do
+     test "runs webserver on customized port" do
+       {nickname, url, url_404} =
+         within @federated1 do
+-          import Pleroma.Web.Router.Helpers
+           user = Pleroma.Factory.insert(:user)
+-          user_url = account_url(Pleroma.Web.Endpoint, :show, user)
+-          url_404 = account_url(Pleroma.Web.Endpoint, :show, "not-exists")
++          user_url = ~p[/api/v1/accounts/#{user}]
++          url_404 = ~p"/api/v1/accounts/not-exists"
+ 
+           {user.nickname, user_url, url_404}
+         end
+diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
+index c33528a67..d067d6a92 100644
+--- a/test/pleroma/user_test.exs
++++ b/test/pleroma/user_test.exs
+@@ -1004,23 +1004,13 @@ test "it doesn't fail on invalid alsoKnownAs entries" do
+   test "returns an ap_id for a user" do
+     user = insert(:user)
+ 
+-    assert User.ap_id(user) ==
+-             Pleroma.Web.Router.Helpers.user_feed_url(
+-               Pleroma.Web.Endpoint,
+-               :feed_redirect,
+-               user.nickname
+-             )
++    assert User.ap_id(user) == url(@endpoint, ~p[/users/#{user.nickname}])
+   end
+ 
+   test "returns an ap_followers link for a user" do
+     user = insert(:user)
+ 
+-    assert User.ap_followers(user) ==
+-             Pleroma.Web.Router.Helpers.user_feed_url(
+-               Pleroma.Web.Endpoint,
+-               :feed_redirect,
+-               user.nickname
+-             ) <> "/followers"
++    assert User.ap_followers(user) == url(@endpoint, ~p[/users/#{user.nickname}/followers])
+   end
+ 
+   describe "remote user changeset" do
+diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+index 4ef0c9302..dcb5f143c 100644
+--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
++++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+@@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
+     test "with the relay active, it returns the relay user", %{conn: conn} do
+       res =
+         conn
+-        |> get(activity_pub_path(conn, :relay))
++        |> get(~p"/relay")
+         |> json_response(200)
+ 
+       assert res["id"] =~ "/relay"
+@@ -49,7 +49,7 @@ test "with the relay disabled, it returns 404", %{conn: conn} do
+       clear_config([:instance, :allow_relay], false)
+ 
+       conn
+-      |> get(activity_pub_path(conn, :relay))
++      |> get(~p"/relay")
+       |> json_response(404)
+     end
+ 
+@@ -59,7 +59,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do
+ 
+       conn
+       |> assign(:user, user)
+-      |> get(activity_pub_path(conn, :relay))
++      |> get(~p"/relay")
+       |> json_response(404)
+     end
+   end
+@@ -68,7 +68,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do
+     test "it returns the internal fetch user", %{conn: conn} do
+       res =
+         conn
+-        |> get(activity_pub_path(conn, :internal_fetch))
++        |> get(~p"/internal/fetch")
+         |> json_response(200)
+ 
+       assert res["id"] =~ "/fetch"
+@@ -80,7 +80,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do
+ 
+       conn
+       |> assign(:user, user)
+-      |> get(activity_pub_path(conn, :internal_fetch))
++      |> get(~p"/internal/fetch")
+       |> json_response(404)
+     end
+   end
+diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
+index d4d1c2aac..5d5388cf5 100644
+--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
++++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
+@@ -2629,6 +2629,14 @@ test "allow fetching of accounts with an empty string name field" do
+     assert user.name == " "
+   end
+ 
++  test "pin_data_from_featured_collection will ignore unsupported values" do
++    assert %{} ==
++             ActivityPub.pin_data_from_featured_collection(%{
++               "type" => "CollectionThatIsNotRealAndCannotHurtMe",
++               "first" => "https://social.example/users/alice/collections/featured?page=true"
++             })
++  end
++
+   describe "persist/1" do
+     test "should not persist remote delete activities" do
+       poster = insert(:user, local: false)
+diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
+index e0a2cb9de..68d77ae5a 100644
+--- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
++++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
+@@ -783,7 +783,8 @@ test "it confirms emails of two users", %{conn: conn, admin: admin} do
+ 
+   describe "PATCH /resend_confirmation_email" do
+     test "it resend emails for two users", %{conn: conn, admin: admin} do
+-      [first_user, second_user] = insert_pair(:user, is_confirmed: false)
++      [first_user, second_user] =
++        insert_pair(:user, is_confirmed: false, confirmation_token: "something")
+ 
+       ret_conn =
+         patch(conn, "/api/v1/pleroma/admin/users/resend_confirmation_email", %{
+diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
+index 2ec9ca969..ace71785b 100644
+--- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs
++++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
+@@ -212,7 +212,7 @@ test "updates state of multiple reports", %{
+     test "returns empty response when no reports created", %{conn: conn} do
+       response =
+         conn
+-        |> get(report_path(conn, :index))
++        |> get(~p"/api/v1/pleroma/admin/reports")
+         |> json_response_and_validate_schema(:ok)
+ 
+       assert Enum.empty?(response["reports"])
+@@ -232,7 +232,7 @@ test "returns reports", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(report_path(conn, :index))
++        |> get(~p"/api/v1/pleroma/admin/reports")
+         |> json_response_and_validate_schema(:ok)
+ 
+       [report] = response["reports"]
+@@ -264,7 +264,7 @@ test "returns reports with specified state", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(report_path(conn, :index, %{state: "open"}))
++        |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "open"]}])
+         |> json_response_and_validate_schema(:ok)
+ 
+       assert [open_report] = response["reports"]
+@@ -276,7 +276,7 @@ test "returns reports with specified state", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(report_path(conn, :index, %{state: "closed"}))
++        |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "closed"]}])
+         |> json_response_and_validate_schema(:ok)
+ 
+       assert [closed_report] = response["reports"]
+@@ -288,7 +288,7 @@ test "returns reports with specified state", %{conn: conn} do
+ 
+       assert %{"total" => 0, "reports" => []} ==
+                conn
+-               |> get(report_path(conn, :index, %{state: "resolved"}))
++               |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "resolved"]}])
+                |> json_response_and_validate_schema(:ok)
+     end
+ 
+diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+index ba6465630..6dcef1a39 100644
+--- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs
++++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+@@ -685,7 +685,7 @@ test "load users with actor_type is Person", %{admin: admin, conn: conn} do
+ 
+       response =
+         conn
+-        |> get(user_path(conn, :index), %{actor_types: ["Person"]})
++        |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Person"]})
+         |> json_response_and_validate_schema(200)
+ 
+       users = [
+@@ -706,7 +706,7 @@ test "load users with actor_type is Person and Service", %{admin: admin, conn: c
+ 
+       response =
+         conn
+-        |> get(user_path(conn, :index), %{actor_types: ["Person", "Service"]})
++        |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Person", "Service"]})
+         |> json_response_and_validate_schema(200)
+ 
+       users = [
+@@ -727,7 +727,7 @@ test "load users with actor_type is Service", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> get(user_path(conn, :index), %{actor_types: ["Service"]})
++        |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Service"]})
+         |> json_response_and_validate_schema(200)
+ 
+       users = [user_response(user_service, %{"actor_type" => "Service"})]
+diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
+index fe7940f41..f67d5485d 100644
+--- a/test/pleroma/web/feed/tag_controller_test.exs
++++ b/test/pleroma/web/feed/tag_controller_test.exs
+@@ -50,7 +50,7 @@ test "gets a feed (ATOM)", %{conn: conn} do
+     response =
+       conn
+       |> put_req_header("accept", "application/atom+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.atom"))
++      |> get(~p"/tags/pleromaart.atom")
+       |> response(200)
+ 
+     xml = parse(response)
+@@ -117,7 +117,7 @@ test "gets a feed (RSS)", %{conn: conn} do
+     response =
+       conn
+       |> put_req_header("accept", "application/rss+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
++      |> get(~p"/tags/pleromaart.rss")
+       |> response(200)
+ 
+     xml = parse(response)
+@@ -157,7 +157,7 @@ test "gets a feed (RSS)", %{conn: conn} do
+     response =
+       conn
+       |> put_req_header("accept", "application/rss+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
++      |> get(~p"/tags/pleromaart.rss")
+       |> response(200)
+ 
+     xml = parse(response)
+@@ -188,7 +188,7 @@ test "gets a feed (RSS)", %{conn: conn} do
+     test "returns 404 for tags feed", %{conn: conn} do
+       conn
+       |> put_req_header("accept", "application/rss+xml")
+-      |> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
++      |> get(~p"/tags/pleromaart.rss")
+       |> response(404)
+     end
+   end
+diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
+index 245ffcf0a..d5d9faf06 100644
+--- a/test/pleroma/web/feed/user_controller_test.exs
++++ b/test/pleroma/web/feed/user_controller_test.exs
+@@ -66,7 +66,7 @@ test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_
+       resp =
+         conn
+         |> put_req_header("accept", "application/atom+xml")
+-        |> get(user_feed_path(conn, :feed, user.nickname))
++        |> get(~p[/users/#{user.nickname}/feed])
+         |> response(200)
+ 
+       activity_titles =
+@@ -128,7 +128,7 @@ test "returns 404 for a missing feed", %{conn: conn} do
+       conn =
+         conn
+         |> put_req_header("accept", "application/atom+xml")
+-        |> get(user_feed_path(conn, :feed, "nonexisting"))
++        |> get(~p"/users/nonexisting/feed")
+ 
+       assert response(conn, 404)
+     end
+@@ -144,7 +144,7 @@ test "returns feed with public and unlisted activities", %{conn: conn} do
+       resp =
+         conn
+         |> put_req_header("accept", "application/atom+xml")
+-        |> get(user_feed_path(conn, :feed, user.nickname))
++        |> get(~p[/users/#{user.nickname}/feed])
+         |> response(200)
+ 
+       activity_titles =
+@@ -163,7 +163,7 @@ test "returns 404 when the user is remote", %{conn: conn} do
+ 
+       assert conn
+              |> put_req_header("accept", "application/atom+xml")
+-             |> get(user_feed_path(conn, :feed, user.nickname))
++             |> get(~p[/users/#{user.nickname}/feed])
+              |> response(404)
+     end
+ 
+@@ -240,7 +240,7 @@ test "with non-html / non-json format, it returns error when user is not found",
+       response =
+         conn
+         |> put_req_header("accept", "application/xml")
+-        |> get(user_feed_path(conn, :feed, "jimm"))
++        |> get(~p"/users/jimm/feed")
+         |> response(404)
+ 
+       assert response == ~S({"error":"Not found"})
+@@ -258,7 +258,7 @@ test "returns 404 for user feed", %{conn: conn} do
+ 
+       assert conn
+              |> put_req_header("accept", "application/atom+xml")
+-             |> get(user_feed_path(conn, :feed, user.nickname))
++             |> get(~p[/users/#{user.nickname}/feed])
+              |> response(404)
+     end
+   end
+diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs
+index a347c7987..ebd536b0b 100644
+--- a/test/pleroma/web/mastodon_api/update_credentials_test.exs
++++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs
+@@ -529,7 +529,7 @@ test "update fields with a link to content with rel=me, with frontend path", %{
+       user: user,
+       conn: conn
+     } do
+-      fe_url = "#{Pleroma.Web.Endpoint.url()}/#{user.nickname}"
++      fe_url = url(~p[/#{user.nickname}])
+ 
+       Tesla.Mock.mock(fn
+         %{url: "http://example.com/rel_me/fe_path"} ->
+diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
+index 682c633f4..22f65a0d1 100644
+--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
++++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
+@@ -258,7 +258,7 @@ test "a note activity" do
+     expected = %{
+       id: to_string(note.id),
+       uri: object_data["id"],
+-      url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
++      url: url(~p[/notice/#{note}]),
+       account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
+       in_reply_to_id: nil,
+       in_reply_to_account_id: nil,
+diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
+index 731447094..cd3f5eced 100644
+--- a/test/pleroma/web/metadata/providers/twitter_card_test.exs
++++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs
+@@ -12,7 +12,6 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
+   alias Pleroma.Web.MediaProxy
+   alias Pleroma.Web.Metadata.Providers.TwitterCard
+   alias Pleroma.Web.Metadata.Utils
+-  alias Pleroma.Web.Router
+ 
+   setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
+ 
+@@ -189,7 +188,7 @@ test "it renders supported types of attachments and skips unknown types" do
+              {:meta,
+               [
+                 name: "twitter:player",
+-                content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
++                content: url(Endpoint, ~p[/notice/#{activity.id}/embed_player])
+               ], []},
+              {:meta, [name: "twitter:player:width", content: "800"], []},
+              {:meta, [name: "twitter:player:height", content: "600"], []},
+diff --git a/test/pleroma/web/mongoose_im_controller_test.exs b/test/pleroma/web/mongoose_im_controller_test.exs
+index bda7c613f..cf7db6d93 100644
+--- a/test/pleroma/web/mongoose_im_controller_test.exs
++++ b/test/pleroma/web/mongoose_im_controller_test.exs
+@@ -13,28 +13,28 @@ test "/user_exists", %{conn: conn} do
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "lain")
++      |> get(~p"/user_exists", user: "lain")
+       |> json_response(200)
+ 
+     assert res == true
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "alice")
++      |> get(~p"/user_exists", user: "alice")
+       |> json_response(404)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "bob")
++      |> get(~p"/user_exists", user: "bob")
+       |> json_response(404)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :user_exists), user: "konata")
++      |> get(~p"/user_exists", user: "konata")
+       |> json_response(404)
+ 
+     assert res == false
+@@ -52,28 +52,28 @@ test "/check_password", %{conn: conn} do
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool")
++      |> get(~p"/check_password", user: user.nickname, pass: "cool")
+       |> json_response(200)
+ 
+     assert res == true
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool")
++      |> get(~p"/check_password", user: user.nickname, pass: "uncool")
+       |> json_response(403)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "cool")
++      |> get(~p"/check_password", user: "konata", pass: "cool")
+       |> json_response(404)
+ 
+     assert res == false
+ 
+     res =
+       conn
+-      |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")
++      |> get(~p"/check_password", user: "nobody", pass: "cool")
+       |> json_response(404)
+ 
+     assert res == false
+diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs
+index 3748940fd..e0ba339db 100644
+--- a/test/pleroma/web/o_auth/o_auth_controller_test.exs
++++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs
+@@ -57,7 +57,7 @@ test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %
+ 
+       assert response = html_response(conn, 200)
+       assert response =~ "Sign in with Twitter"
+-      assert response =~ o_auth_path(conn, :prepare_request)
++      assert response =~ ~p"/prepare_request"
+     end
+ 
+     test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
+@@ -186,7 +186,9 @@ test "on authentication error, GET /oauth/<provider>/callback redirects to `redi
+ 
+       assert html_response(conn, 302)
+       assert redirected_to(conn) == app.redirect_uris
+-      assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
++
++      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
++               "Failed to authenticate: (error description)."
+     end
+ 
+     test "GET /oauth/registration_details renders registration details form", %{
+@@ -307,7 +309,9 @@ test "with invalid params, POST /oauth/register?op=register renders registration
+           |> post("/oauth/register", bad_params)
+ 
+         assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
+-        assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
++
++        assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
++                 "Error: #{bad_param} has already been taken."
+       end
+     end
+ 
+@@ -398,7 +402,7 @@ test "with invalid params, POST /oauth/register?op=connect renders registration_
+         |> post("/oauth/register", params)
+ 
+       assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
+-      assert get_flash(conn, :error) == "Invalid Username/Password"
++      assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid Username/Password"
+     end
+   end
+ 
+diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+index dc300296c..d2bc7840f 100644
+--- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
++++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+@@ -48,9 +48,7 @@ test "adds status to pleroma instance if the `acct` is a status", %{conn: conn}
+ 
+       assert conn
+              |> get(
+-               remote_follow_path(conn, :follow, %{
+-                 acct: "https://mastodon.social/users/emelie/statuses/101849165031453009"
+-               })
++               ~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie/statuses/101849165031453009"]}]
+              )
+              |> redirected_to() =~ "/notice/"
+     end
+@@ -77,7 +75,7 @@ test "show follow account page if the `acct` is a account link", %{conn: conn} d
+ 
+       response =
+         conn
+-        |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
++        |> get(~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie"]}])
+         |> html_response(200)
+ 
+       assert response =~ "Log in to follow"
+@@ -108,7 +106,7 @@ test "show follow page if the `acct` is a account link", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}))
++        |> get(~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie"]}])
+         |> html_response(200)
+ 
+       assert response =~ "Remote follow"
+@@ -129,9 +127,7 @@ test "show follow page with error when user can not be fetched by `acct` link",
+                  conn
+                  |> assign(:user, user)
+                  |> get(
+-                   remote_follow_path(conn, :follow, %{
+-                     acct: "https://mastodon.social/users/not_found"
+-                   })
++                   ~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/not_found"]}]
+                  )
+                  |> html_response(200)
+ 
+@@ -151,7 +147,7 @@ test "required `follow | write:follows` scope", %{conn: conn} do
+                  conn
+                  |> assign(:user, user)
+                  |> assign(:token, read_token)
+-                 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++                 |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+                  |> response(200)
+ 
+                assert response =~ "Error following account"
+@@ -166,7 +162,7 @@ test "follows user", %{conn: conn} do
+         conn
+         |> assign(:user, user)
+         |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ 
+       assert redirected_to(conn) == "/users/#{user2.id}"
+     end
+@@ -178,7 +174,7 @@ test "returns error when user is deactivated", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+         |> response(200)
+ 
+       assert response =~ "Error following account"
+@@ -194,7 +190,7 @@ test "returns error when user is blocked", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+         |> response(200)
+ 
+       assert response =~ "Error following account"
+@@ -206,7 +202,7 @@ test "returns error when followee not found", %{conn: conn} do
+       response =
+         conn
+         |> assign(:user, user)
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => "jimm"}})
+         |> response(200)
+ 
+       assert response =~ "Error following account"
+@@ -221,7 +217,7 @@ test "returns success result when user already in followers", %{conn: conn} do
+         conn
+         |> assign(:user, refresh_record(user))
+         |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"]))
+-        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}})
++        |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}})
+ 
+       assert redirected_to(conn) == "/users/#{user2.id}"
+     end
+@@ -243,7 +239,7 @@ test "render the MFA login form", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+         })
+         |> response(200)
+@@ -271,7 +267,7 @@ test "returns error when password is incorrect", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id}
+         })
+         |> response(200)
+@@ -299,7 +295,7 @@ test "follows", %{conn: conn} do
+       conn =
+         conn
+         |> post(
+-          remote_follow_path(conn, :do_follow),
++          ~p"/ostatus_subscribe",
+           %{
+             "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
+           }
+@@ -328,7 +324,7 @@ test "returns error when auth code is incorrect", %{conn: conn} do
+       response =
+         conn
+         |> post(
+-          remote_follow_path(conn, :do_follow),
++          ~p"/ostatus_subscribe",
+           %{
+             "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id}
+           }
+@@ -347,7 +343,7 @@ test "follows", %{conn: conn} do
+ 
+       conn =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+         })
+ 
+@@ -360,7 +356,7 @@ test "returns error when followee not found", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"}
+         })
+         |> response(200)
+@@ -373,7 +369,7 @@ test "returns error when login invalid", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id}
+         })
+         |> response(200)
+@@ -387,7 +383,7 @@ test "returns error when password invalid", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id}
+         })
+         |> response(200)
+@@ -403,7 +399,7 @@ test "returns error when user is blocked", %{conn: conn} do
+ 
+       response =
+         conn
+-        |> post(remote_follow_path(conn, :do_follow), %{
++        |> post(~p"/ostatus_subscribe", %{
+           "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id}
+         })
+         |> response(200)
+diff --git a/test/pleroma/web/uploader_controller_test.exs b/test/pleroma/web/uploader_controller_test.exs
+index 032895e71..e11a7367f 100644
+--- a/test/pleroma/web/uploader_controller_test.exs
++++ b/test/pleroma/web/uploader_controller_test.exs
+@@ -10,7 +10,7 @@ defmodule Pleroma.Web.UploaderControllerTest do
+     test "it returns 400 response when process callback isn't alive", %{conn: conn} do
+       res =
+         conn
+-        |> post(uploader_path(conn, :callback, "test-path"))
++        |> post(~p"/api/v1/pleroma/uploader_callback/test-path")
+ 
+       assert res.status == 400
+       assert res.resp_body == "{\"error\":\"bad request\"}"
+@@ -34,7 +34,7 @@ test "it returns success result", %{conn: conn} do
+ 
+       res =
+         conn
+-        |> post(uploader_path(conn, :callback, "test-path"))
++        |> post(~p"/api/v1/pleroma/uploader_callback/test-path")
+         |> json_response(200)
+ 
+       assert res == %{"upload_path" => "test-path"}
+diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
+index eab469833..21f031bba 100644
+--- a/test/support/conn_case.ex
++++ b/test/support/conn_case.ex
+@@ -27,12 +27,12 @@ defmodule Pleroma.Web.ConnCase do
+       import Plug.Conn
+       import Phoenix.ConnTest
+       use Pleroma.Tests.Helpers
+-      import Pleroma.Web.Router.Helpers
+ 
+       alias Pleroma.Config
+ 
+       # The default endpoint for testing
+       @endpoint Pleroma.Web.Endpoint
++      use Pleroma.Web, :verified_routes
+ 
+       # Sets up OAuth access with specified scopes
+       defp oauth_access(scopes, opts \\ []) do
+diff --git a/test/support/data_case.ex b/test/support/data_case.ex
+index 0ee2aa4a2..5eacc148d 100644
+--- a/test/support/data_case.ex
++++ b/test/support/data_case.ex
+@@ -23,12 +23,14 @@ defmodule Pleroma.DataCase do
+   using do
+     quote do
+       alias Pleroma.Repo
++      @endpoint Pleroma.Web.Endpoint
+ 
+       import Ecto
+       import Ecto.Changeset
+       import Ecto.Query
+       import Pleroma.DataCase
+       use Pleroma.Tests.Helpers
++      use Pleroma.Web, :verified_routes
+ 
+       # Sets up OAuth access with specified scopes
+       defp oauth_access(scopes, opts \\ []) do
+-- 
+2.43.0
+
diff --git a/pkgs/akkoma/0002-Bump-lockfile.patch b/pkgs/akkoma/0002-Bump-lockfile.patch
new file mode 100644
index 0000000..824d94a
--- /dev/null
+++ b/pkgs/akkoma/0002-Bump-lockfile.patch
@@ -0,0 +1,203 @@
+From 79667c3a0059a578ab3242cde7ecfe2ef7077a30 Mon Sep 17 00:00:00 2001
+From: sefidel <contact@sefidel.net>
+Date: Mon, 19 Feb 2024 21:10:05 +0900
+Subject: [PATCH 2/3] Bump lockfile
+
+Signed-off-by: sefidel <contact@sefidel.net>
+---
+ mix.lock | 106 +++++++++++++++++++++++++++----------------------------
+ 1 file changed, 53 insertions(+), 53 deletions(-)
+
+diff --git a/mix.lock b/mix.lock
+index 3817ec210..4fe25553d 100644
+--- a/mix.lock
++++ b/mix.lock
+@@ -1,132 +1,132 @@
+ %{
+-  "argon2_elixir": {:hex, :argon2_elixir, "3.1.0", "4135e0a1b4ff800d42c85aa663e068efa3cb356297189b5b65caa992db8ec8cf", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c08feae0ee0292165d1b945003363c7cd8523d002e0483c627dfca930291dd73"},
++  "argon2_elixir": {:hex, :argon2_elixir, "3.2.1", "f47740bf9f2a39ffef79ba48eb25dea2ee37bcc7eadf91d49615591d1a6fce1a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "a813b78217394530b5fcf4c8070feee43df03ffef938d044019169c766315690"},
+   "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
+   "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
+   "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
+-  "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"},
+-  "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
++  "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
++  "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
+   "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
+   "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
+   "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "3bbfa8b5ea13accc1b1c40579a380d8e5cfd6ad2", [ref: "3bbfa8b5ea13accc1b1c40579a380d8e5cfd6ad2"]},
+-  "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"},
+-  "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
++  "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
++  "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
+   "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
+-  "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
++  "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
+   "concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
+   "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
+   "cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
+   "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
+   "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
+   "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
+-  "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [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", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
++  "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"},
+   "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
+-  "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
++  "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
+   "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
+   "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
+-  "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"},
+-  "earmark": {:hex, :earmark, "1.4.39", "acdb2f02c536471029dbcc509fbd6b94b89f40ad7729fb3f68f4b6944843f01d", [:mix], [{:earmark_parser, "~> 1.4.33", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "156c9d8ec3cbeccdbf26216d8247bdeeacc8c76b4d9eee7554be2f1b623ea440"},
+-  "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
++  "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
++  "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
++  "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
+   "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
+   "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [: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", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
+   "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
+-  "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.12", "e3bd8318702b069263d0118e7cdb6c66c5ff0a034f540f4c0158bd769e7dff6a", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "4a1d1d10b74ce033a428a99272038c90e444a0a1912a074e38a71ee9f667714c"},
+-  "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
++  "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.15", "0fc29dbae0e444a29bd6abeee4cf3c4c037e692a272478a234a1cc765077dbb1", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "b6127f3a5c6fc3d84895e4768cc7c199f22b48b67d6c99b13fbf4a374e73f039"},
++  "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
+   "elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
+   "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
+   "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+   "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
+-  "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"},
+-  "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
++  "ex_aws": {:hex, :ex_aws, "2.5.1", "7418917974ea42e9e84b25e88b9f3d21a861d5f953ad453e212f48e593d8d39f", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b95431f70c446fa1871f0eb9b183043c5a625f75f9948a42d25f43ae2eff12b"},
++  "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
+   "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"},
+-  "ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"},
++  "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"},
+   "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
+   "ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
+   "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
+   "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
+-  "fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"},
++  "fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
+   "fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
+-  "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
++  "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
+   "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
+   "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
+-  "floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"},
++  "floki": {:hex, :floki, "0.35.3", "0c8c6234aa71cb2b069cf801e8f8f30f8d096eb452c3dae2ccc409510ec32720", [:mix], [], "hexpm", "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4"},
+   "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
+   "gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
+-  "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"},
++  "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.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.4.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", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
+   "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
+   "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
+-  "http_signatures": {:hex, :http_signatures, "0.1.1", "ca7ebc1b61542b163644c8c3b1f0e0f41037d35f2395940d3c6c7deceab41fd8", [:mix], [], "hexpm", "cc3b8a007322cc7b624c0c15eec49ee58ac977254ff529a3c482f681465942a3"},
++  "http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"},
+   "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
+   "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
+-  "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
++  "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
+   "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
+   "joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
+   "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
+-  "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
++  "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
+   "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []},
+   "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
+   "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
+-  "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": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
+   "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
+-  "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
++  "makeup_erlang": {:hex, :makeup_erlang, "0.1.4", "29563475afa9b8a2add1b7a9c8fb68d06ca7737648f28398e04461f008b69521", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29"},
+   "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
+   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
+   "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "912fba81152d4d572e457fd5427f9875b2bc3dbe", [ref: "912fba81152d4d572e457fd5427f9875b2bc3dbe"]},
+   "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
+   "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
+-  "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"},
++  "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
+   "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
+   "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
+-  "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"},
+-  "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
+-  "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
++  "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
++  "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
++  "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
+   "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
+-  "oban": {:hex, :oban, "2.15.2", "8f934a49db39163633965139c8846d8e24c2beb4180f34a005c2c7c3f69a6aa2", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0f4a579ea48fc7489e0d84facf8b01566e142bdc6542d7dabce32c10e664f1e9"},
+-  "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"},
+-  "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
+-  "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [: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.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"},
+-  "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
+-  "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"},
++  "oban": {:hex, :oban, "2.15.4", "d49ab4ffb7153010e32f80fe9e56f592706238149ec579eb50f8a4e41d218856", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c"},
++  "open_api_spex": {:hex, :open_api_spex, "3.18.2", "8c855e83bfe8bf81603d919d6e892541eafece3720f34d1700b58024dadde247", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b"},
++  "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
++  "phoenix": {:hex, :phoenix, "1.7.11", "1d88fc6b05ab0c735b250932c4e6e33bfa1c186f76dcf623d8dd52f07d6379c7", [: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.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "b1ec57f2e40316b306708fe59b92a16b9f6f4bf50ccfa41aa8c7feb79e0ec02a"},
++  "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},
++  "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
+   "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
+   "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, 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]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
+   "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
+-  "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
+-  "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"},
+-  "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
+-  "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
+-  "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [: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", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
+-  "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
++  "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
++  "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
++  "phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
++  "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
++  "plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [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", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"},
++  "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
+   "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
+   "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
+   "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
+-  "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{: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", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"},
++  "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{: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", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"},
+   "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
+   "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
+-  "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"},
++  "recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"},
+   "remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
+   "search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
+   "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
+   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
+   "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
+-  "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
+-  "swoosh": {:hex, :swoosh, "1.11.5", "429dccde78e2f60c6339e96917efecebca9d1f254d2878a150f580d2f782260b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21ee57dcd68d2f56d3bbe11e76d56d142b221bb12b6018c551cc68442b800040"},
++  "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
++  "swoosh": {:hex, :swoosh, "1.15.2", "490ea85a98e8fb5178c07039e0d8519839e38127724a58947a668c00db7574ee", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9f7739c02f6c7c0ca82ee397f3bfe0465dbe4c8a65372ac2a5584bf147dd5831"},
+   "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
+-  "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
++  "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
+   "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
+-  "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
++  "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
+   "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"},
+   "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
+   "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
+   "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
+-  "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"},
++  "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
+   "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
+   "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
+   "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
+-  "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
++  "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"},
+   "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
+-  "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
+-  "vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"},
++  "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
++  "vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"},
+   "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
+-  "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"},
+-  "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"},
++  "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
++  "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"},
+   "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
+ }
+-- 
+2.43.0
+
diff --git a/pkgs/akkoma/0003-Fix-OAuth-consumer-mode.patch b/pkgs/akkoma/0003-Fix-OAuth-consumer-mode.patch
new file mode 100644
index 0000000..b88cfd0
--- /dev/null
+++ b/pkgs/akkoma/0003-Fix-OAuth-consumer-mode.patch
@@ -0,0 +1,248 @@
+From 548364d124e729ed3ec97ef2ad063b7adace5730 Mon Sep 17 00:00:00 2001
+From: sefidel <contact@sefidel.net>
+Date: Mon, 19 Feb 2024 21:11:31 +0900
+Subject: [PATCH 3/3] Fix OAuth consumer mode
+
+Original commit: https://akkoma.dev/AkkomaGang/akkoma/pulls/668
+Backported to v3.10.4
+
+Signed-off-by: sefidel <contact@sefidel.net>
+---
+ docs/docs/configuration/cheatsheet.md         |  9 ++++
+ lib/pleroma/web/o_auth/o_auth_controller.ex   | 37 ++++++++-------
+ mix.exs                                       |  2 +-
+ mix.lock                                      |  2 +-
+ .../web/o_auth/o_auth_controller_test.exs     | 47 ++++++++++++++-----
+ 5 files changed, 67 insertions(+), 30 deletions(-)
+
+diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md
+index 73fdf9eea..2f53f0c78 100644
+--- a/docs/docs/configuration/cheatsheet.md
++++ b/docs/docs/configuration/cheatsheet.md
+@@ -958,6 +958,15 @@ config :ueberauth, Ueberauth,
+   ]
+ ```
+ 
++You may also need to set up your frontend to use oauth logins. For example, for `akkoma-fe`:
++
++```elixir
++config :pleroma, :frontend_configurations,
++  pleroma_fe: %{
++    loginMethod: "token"
++  }
++```
++
+ ## Link parsing
+ 
+ ### :uri_schemes
+diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
+index ba33dc9e7..29aa8c10e 100644
+--- a/lib/pleroma/web/o_auth/o_auth_controller.ex
++++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
+@@ -39,6 +39,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
+   action_fallback(Pleroma.Web.OAuth.FallbackController)
+ 
+   @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
++  @state_cookie_name "akkoma_oauth_state"
+ 
+   # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
+   def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
+@@ -443,13 +444,10 @@ def prepare_request(%Plug.Conn{} = conn, %{
+       |> Map.put("scope", scope)
+       |> Jason.encode!()
+ 
+-    params =
+-      auth_attrs
+-      |> Map.drop(~w(scope scopes client_id redirect_uri))
+-      |> Map.put("state", state)
+-
+     # Handing the request to Ueberauth
+-    redirect(conn, to: ~p"/oauth/#{provider}?#{params}")
++    conn
++    |> put_resp_cookie(@state_cookie_name, state)
++    |> redirect(to: ~p"/oauth/#{provider}")
+   end
+ 
+   def request(%Plug.Conn{} = conn, params) do
+@@ -468,20 +466,26 @@ def request(%Plug.Conn{} = conn, params) do
+   end
+ 
+   def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
+-    params = callback_params(params)
++    params = callback_params(conn, params)
+     messages = for e <- Map.get(failure, :errors, []), do: e.message
+     message = Enum.join(messages, "; ")
+ 
+-    conn
+-    |> put_flash(
+-      :error,
+-      dgettext("errors", "Failed to authenticate: %{message}.", message: message)
+-    )
+-    |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
++    error_message = dgettext("errors", "Failed to authenticate: %{message}.", message: message)
++
++    if params["redirect_uri"] do
++      conn
++      |> put_flash(
++        :error,
++        error_message
++      )
++      |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
++    else
++      send_resp(conn, :bad_request, error_message)
++    end
+   end
+ 
+   def callback(%Plug.Conn{} = conn, params) do
+-    params = callback_params(params)
++    params = callback_params(conn, params)
+ 
+     with {:ok, registration} <- Authenticator.get_registration(conn) do
+       auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
+@@ -511,8 +515,9 @@ def callback(%Plug.Conn{} = conn, params) do
+     end
+   end
+ 
+-  defp callback_params(%{"state" => state} = params) do
+-    Map.merge(params, Jason.decode!(state))
++  defp callback_params(%Plug.Conn{} = conn, params) do
++    fetch_cookies(conn)
++    Map.merge(params, Jason.decode!(Map.get(conn.req_cookies, @state_cookie_name, "{}")))
+   end
+ 
+   def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
+diff --git a/mix.exs b/mix.exs
+index 01a6e8a82..563c61c30 100644
+--- a/mix.exs
++++ b/mix.exs
+@@ -156,7 +156,7 @@ defp deps do
+       {:ex_syslogger, "~> 2.0.0"},
+       {:floki, "~> 0.34"},
+       {:timex, "~> 3.7"},
+-      {:ueberauth, "~> 0.10"},
++      {:ueberauth, "== 0.10.5"},
+       {:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git"},
+       {:http_signatures, "~> 0.1.1"},
+       {:telemetry, "~> 1.2"},
+diff --git a/mix.lock b/mix.lock
+index 4fe25553d..cb6a6e040 100644
+--- a/mix.lock
++++ b/mix.lock
+@@ -121,7 +121,7 @@
+   "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
+   "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
+   "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
+-  "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"},
++  "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
+   "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
+   "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
+   "vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"},
+diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs
+index e0ba339db..d0703f44c 100644
+--- a/test/pleroma/web/o_auth/o_auth_controller_test.exs
++++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs
+@@ -81,9 +81,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %
+ 
+       assert html_response(conn, 302)
+ 
+-      redirect_query = URI.parse(redirected_to(conn)).query
+-      assert %{"state" => state_param} = URI.decode_query(redirect_query)
+-      assert {:ok, state_components} = Jason.decode(state_param)
++      assert {:ok, state_components} = Jason.decode(conn.resp_cookies["akkoma_oauth_state"].value)
+ 
+       expected_client_id = app.client_id
+       expected_redirect_uri = app.redirect_uris
+@@ -97,7 +95,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %
+     end
+ 
+     test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
+-         %{app: app, conn: conn} do
++         %{app: app, conn: _} do
+       registration = insert(:registration)
+       redirect_uri = OAuthController.default_redirect_uri(app)
+ 
+@@ -109,15 +107,17 @@ test "with user-bound registration, GET /oauth/<provider>/callback redirects to
+       }
+ 
+       conn =
+-        conn
++        build_conn()
++        |> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params))
++        |> Plug.Session.call(Plug.Session.init(@session_opts))
++        |> fetch_session()
+         |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
+         |> get(
+           "/oauth/twitter/callback",
+           %{
+             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+-            "provider" => "twitter",
+-            "state" => Jason.encode!(state_params)
++            "provider" => "twitter"
+           }
+         )
+ 
+@@ -162,17 +162,19 @@ test "with user-unbound registration, GET /oauth/<provider>/callback renders reg
+ 
+     test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
+       app: app,
+-      conn: conn
++      conn: _
+     } do
+       state_params = %{
+         "scope" => Enum.join(app.scopes, " "),
+         "client_id" => app.client_id,
+-        "redirect_uri" => OAuthController.default_redirect_uri(app),
+-        "state" => ""
++        "redirect_uri" => OAuthController.default_redirect_uri(app)
+       }
+ 
+       conn =
+-        conn
++        build_conn()
++        |> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params))
++        |> Plug.Session.call(Plug.Session.init(@session_opts))
++        |> fetch_session()
+         |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
+         |> get(
+           "/oauth/twitter/callback",
+@@ -180,7 +182,7 @@ test "on authentication error, GET /oauth/<provider>/callback redirects to `redi
+             "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
+             "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
+             "provider" => "twitter",
+-            "state" => Jason.encode!(state_params)
++            "state" => ""
+           }
+         )
+ 
+@@ -191,6 +193,27 @@ test "on authentication error, GET /oauth/<provider>/callback redirects to `redi
+                "Failed to authenticate: (error description)."
+     end
+ 
++    test "on authentication error with no prior state, GET /oauth/<provider>/callback returns 400",
++         %{
++           app: _,
++           conn: conn
++         } do
++      conn =
++        conn
++        |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
++        |> get(
++          "/oauth/twitter/callback",
++          %{
++            "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
++            "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
++            "provider" => "twitter",
++            "state" => ""
++          }
++        )
++
++      assert response(conn, 400)
++    end
++
+     test "GET /oauth/registration_details renders registration details form", %{
+       app: app,
+       conn: conn
+-- 
+2.43.0
+
diff --git a/pkgs/akkoma/default.nix b/pkgs/akkoma/default.nix
new file mode 100644
index 0000000..e771093
--- /dev/null
+++ b/pkgs/akkoma/default.nix
@@ -0,0 +1,197 @@
+{ lib
+, beamPackages
+, fetchFromGitea, fetchFromGitHub, fetchFromGitLab
+, cmake, file
+, writeText
+, nixosTests
+, ...
+}:
+
+beamPackages.mixRelease rec {
+  pname = "pleroma";
+  version = "3.10.4-bp1";
+
+  src = fetchFromGitea {
+    domain = "akkoma.dev";
+    owner = "AkkomaGang";
+    repo = "akkoma";
+    rev = "v3.10.4";
+    hash = "sha256-MPUZFcIxZ21fe3edwi+/Kt8qpwNBCh40wheC3QMqw2M=";
+  };
+
+  # TODO: https://akkoma.dev/AkkomaGang/akkoma/pulls/668
+  # These changes are backported to v3.10.4 since building mime v2.x on Nix
+  # is painful due to its pure nature in mixNixDeps.
+  patches = [
+    ./0001-Migrate-to-phoenix-1.7.patch
+    ./0002-Bump-lockfile.patch
+    ./0003-Fix-OAuth-consumer-mode.patch
+  ];
+
+  postPatch = ''
+    # Remove dependency on OS_Mon
+    sed -E -i 's/(^|\s):os_mon,//' \
+      mix.exs
+  '';
+
+  postBuild = ''
+    # Digest and compress static files
+    rm -f priv/static/READ_THIS_BEFORE_TOUCHING_FILES_HERE
+    mix phx.digest --no-deps-check
+  '';
+
+  mixNixDeps = import ./mix.nix {
+    inherit beamPackages lib;
+    overrides = (final: prev: {
+      # mix2nix does not support git dependencies yet,
+      # so we need to add them manually
+      captcha = beamPackages.buildMix rec {
+        name = "captcha";
+        version = "0.1.0";
+
+        src = fetchFromGitLab {
+          domain = "git.pleroma.social";
+          group = "pleroma";
+          owner = "elixir-libraries";
+          repo = "elixir-captcha";
+          rev = "3bbfa8b5ea13accc1b1c40579a380d8e5cfd6ad2";
+          hash = "sha256-skZ0QwF46lUTfsgACMR0AR5ymY2F50BQy1AUBjWVdro=";
+        };
+
+        # the binary is not getting installed by default
+        postInstall = "mv priv/* $out/lib/erlang/lib/${name}-${version}/priv/";
+      };
+      concurrent_limiter = beamPackages.buildMix rec {
+        name = "concurrent_limiter";
+        version = "0.1.1";
+
+        src = fetchFromGitea {
+          domain = "akkoma.dev";
+          owner = "AkkomaGang";
+          repo = "concurrent-limiter";
+          rev = "a9e0b3d64574bdba761f429bb4fba0cf687b3338";
+          hash = "sha256-A7ucZnXks4K+JDVY5vV2cT5KfEOUOo/OHO4rga5mGys=";
+        };
+      };
+      elasticsearch = beamPackages.buildMix rec {
+        name = "elasticsearch";
+        version = "1.0.1";
+
+        src = fetchFromGitea {
+          domain = "akkoma.dev";
+          owner = "AkkomaGang";
+          repo = "elasticsearch-elixir";
+          rev = "6cd946f75f6ab9042521a009d1d32d29a90113ca";
+          hash = "sha256-CtmQHVl+VTpemne+nxbkYGcErrgCo+t3ZBPbkFSpyF0=";
+        };
+      };
+      linkify = beamPackages.buildMix rec {
+        name = "linkify";
+        version = "0.5.2";
+
+        src = fetchFromGitea {
+          domain = "akkoma.dev";
+          owner = "AkkomaGang";
+          repo = "linkify";
+          rev = "2567e2c1073fa371fd26fd66dfa5bc77b6919c16";
+          hash = "sha256-e3wzlbRuyw/UB5Tb7IozX/WR1T+sIBf9C/o5Thki9vg=";
+        };
+      };
+      mfm_parser = beamPackages.buildMix rec {
+        name = "mfm_parser";
+        version = "0.1.1";
+
+        src = fetchFromGitea {
+          domain = "akkoma.dev";
+          owner = "AkkomaGang";
+          repo = "mfm-parser";
+          rev = "912fba81152d4d572e457fd5427f9875b2bc3dbe";
+          hash = "sha256-n3WmERxKK8VM8jFIBAPS6GkbT7/zjqi3AjjWbjOdMzs=";
+        };
+
+        beamDeps = with final; [ phoenix_view temple ];
+      };
+      search_parser = beamPackages.buildMix rec {
+        name = "search_parser";
+        version = "0.1.0";
+
+        src = fetchFromGitHub {
+          owner = "FloatingGhost";
+          repo = "pleroma-contrib-search-parser";
+          rev = "08971a81e68686f9ac465cfb6661d51c5e4e1e7f";
+          hash = "sha256-sbo9Kcp2oT05o2GAF+IgziLPYmCkWgBfFMBCytmqg3Y=";
+        };
+
+        beamDeps = with final; [ nimble_parsec ];
+      };
+      temple = beamPackages.buildMix rec {
+        name = "temple";
+        version = "0.9.0-rc.0";
+
+        src = fetchFromGitea {
+          domain = "akkoma.dev";
+          owner = "AkkomaGang";
+          repo = "temple";
+          rev = "066a699ade472d8fa42a9d730b29a61af9bc8b59";
+          hash = "sha256-qA0z8WTMjO2OixcZBARn/LbuV3s3LGtwZ9nSjj/tWBc=";
+        };
+
+        mixEnv = "dev";
+        beamDeps = with final; [ earmark_parser ex_doc makeup makeup_elixir makeup_erlang nimble_parsec ];
+      };
+
+
+      # Some additional build inputs and build fixes
+      fast_html = prev.fast_html.override {
+        nativeBuildInputs = [ cmake ];
+        dontUseCmakeConfigure = true;
+      };
+      http_signatures = prev.http_signatures.override {
+        patchPhase = ''
+          substituteInPlace mix.exs --replace ":logger" ":logger, :public_key"
+        '';
+      };
+      majic = prev.majic.override {
+        buildInputs = [ file ];
+      };
+      syslog = prev.syslog.override {
+        buildPlugins = with beamPackages; [ pc ];
+      };
+
+      mime = prev.mime.override {
+        patchPhase = let
+          cfgFile = writeText "config.exs" ''
+            use Mix.Config
+            config :mime, :types, %{
+              "application/activity+json" => ["activity+json"],
+              "application/jrd+json" => ["jrd+json"],
+              "application/ld+json" => ["activity+json"],
+              "application/xml" => ["xml"],
+              "application/xrd+xml" => ["xrd+xml"]
+            }
+          '';
+        in ''
+          mkdir config
+          cp ${cfgFile} config/config.exs
+        '';
+      };
+    });
+  };
+
+  passthru = {
+    tests = with nixosTests; { inherit akkoma akkoma-confined; };
+    inherit mixNixDeps;
+
+    # Used to make sure the service uses the same version of elixir as
+    # the package
+    elixirPackage = beamPackages.elixir;
+  };
+
+  meta = with lib; {
+    description = "ActivityPub microblogging server";
+    homepage = "https://akkoma.social";
+    license = licenses.agpl3;
+    maintainers = with maintainers; [ mvs ];
+    platforms = platforms.unix;
+  };
+}
diff --git a/pkgs/akkoma/mix.nix b/pkgs/akkoma/mix.nix
new file mode 100644
index 0000000..6cb9aac
--- /dev/null
+++ b/pkgs/akkoma/mix.nix
@@ -0,0 +1,1611 @@
+{ lib, beamPackages, overrides ? (x: y: {}) }:
+
+let
+  buildRebar3 = lib.makeOverridable beamPackages.buildRebar3;
+  buildMix = lib.makeOverridable beamPackages.buildMix;
+  buildErlangMk = lib.makeOverridable beamPackages.buildErlangMk;
+
+  self = packages // (overrides self packages);
+
+  packages = with beamPackages; with self; {
+    argon2_elixir = buildMix rec {
+      name = "argon2_elixir";
+      version = "3.2.1";
+
+      src = fetchHex {
+        pkg = "argon2_elixir";
+        version = "${version}";
+        sha256 = "a813b78217394530b5fcf4c8070feee43df03ffef938d044019169c766315690";
+      };
+
+      beamDeps = [ comeonin elixir_make ];
+    };
+
+    base62 = buildMix rec {
+      name = "base62";
+      version = "1.2.2";
+
+      src = fetchHex {
+        pkg = "base62";
+        version = "${version}";
+        sha256 = "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb";
+      };
+
+      beamDeps = [ custom_base ];
+    };
+
+    bbcode_pleroma = buildMix rec {
+      name = "bbcode_pleroma";
+      version = "0.2.0";
+
+      src = fetchHex {
+        pkg = "bbcode_pleroma";
+        version = "${version}";
+        sha256 = "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3";
+      };
+
+      beamDeps = [ nimble_parsec ];
+    };
+
+    bcrypt_elixir = buildMix rec {
+      name = "bcrypt_elixir";
+      version = "3.0.1";
+
+      src = fetchHex {
+        pkg = "bcrypt_elixir";
+        version = "${version}";
+        sha256 = "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf";
+      };
+
+      beamDeps = [ comeonin elixir_make ];
+    };
+
+    benchee = buildMix rec {
+      name = "benchee";
+      version = "1.3.0";
+
+      src = fetchHex {
+        pkg = "benchee";
+        version = "${version}";
+        sha256 = "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81";
+      };
+
+      beamDeps = [ deep_merge statistex ];
+    };
+
+    bunt = buildMix rec {
+      name = "bunt";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "bunt";
+        version = "${version}";
+        sha256 = "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5";
+      };
+
+      beamDeps = [];
+    };
+
+    cachex = buildMix rec {
+      name = "cachex";
+      version = "3.6.0";
+
+      src = fetchHex {
+        pkg = "cachex";
+        version = "${version}";
+        sha256 = "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2";
+      };
+
+      beamDeps = [ eternal jumper sleeplocks unsafe ];
+    };
+
+    calendar = buildMix rec {
+      name = "calendar";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "calendar";
+        version = "${version}";
+        sha256 = "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f";
+      };
+
+      beamDeps = [ tzdata ];
+    };
+
+    castore = buildMix rec {
+      name = "castore";
+      version = "1.0.5";
+
+      src = fetchHex {
+        pkg = "castore";
+        version = "${version}";
+        sha256 = "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578";
+      };
+
+      beamDeps = [];
+    };
+
+    certifi = buildRebar3 rec {
+      name = "certifi";
+      version = "2.12.0";
+
+      src = fetchHex {
+        pkg = "certifi";
+        version = "${version}";
+        sha256 = "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c";
+      };
+
+      beamDeps = [];
+    };
+
+    combine = buildMix rec {
+      name = "combine";
+      version = "0.10.0";
+
+      src = fetchHex {
+        pkg = "combine";
+        version = "${version}";
+        sha256 = "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b";
+      };
+
+      beamDeps = [];
+    };
+
+    comeonin = buildMix rec {
+      name = "comeonin";
+      version = "5.4.0";
+
+      src = fetchHex {
+        pkg = "comeonin";
+        version = "${version}";
+        sha256 = "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1";
+      };
+
+      beamDeps = [];
+    };
+
+    connection = buildMix rec {
+      name = "connection";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "connection";
+        version = "${version}";
+        sha256 = "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c";
+      };
+
+      beamDeps = [];
+    };
+
+    cors_plug = buildMix rec {
+      name = "cors_plug";
+      version = "3.0.3";
+
+      src = fetchHex {
+        pkg = "cors_plug";
+        version = "${version}";
+        sha256 = "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    cowboy = buildErlangMk rec {
+      name = "cowboy";
+      version = "2.10.0";
+
+      src = fetchHex {
+        pkg = "cowboy";
+        version = "${version}";
+        sha256 = "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b";
+      };
+
+      beamDeps = [ cowlib ranch ];
+    };
+
+    cowboy_telemetry = buildRebar3 rec {
+      name = "cowboy_telemetry";
+      version = "0.4.0";
+
+      src = fetchHex {
+        pkg = "cowboy_telemetry";
+        version = "${version}";
+        sha256 = "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de";
+      };
+
+      beamDeps = [ cowboy telemetry ];
+    };
+
+    cowlib = buildRebar3 rec {
+      name = "cowlib";
+      version = "2.12.1";
+
+      src = fetchHex {
+        pkg = "cowlib";
+        version = "${version}";
+        sha256 = "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0";
+      };
+
+      beamDeps = [];
+    };
+
+    credo = buildMix rec {
+      name = "credo";
+      version = "1.7.4";
+
+      src = fetchHex {
+        pkg = "credo";
+        version = "${version}";
+        sha256 = "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2";
+      };
+
+      beamDeps = [ bunt file_system jason ];
+    };
+
+    custom_base = buildMix rec {
+      name = "custom_base";
+      version = "0.2.1";
+
+      src = fetchHex {
+        pkg = "custom_base";
+        version = "${version}";
+        sha256 = "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463";
+      };
+
+      beamDeps = [];
+    };
+
+    db_connection = buildMix rec {
+      name = "db_connection";
+      version = "2.6.0";
+
+      src = fetchHex {
+        pkg = "db_connection";
+        version = "${version}";
+        sha256 = "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3";
+      };
+
+      beamDeps = [ telemetry ];
+    };
+
+    decimal = buildMix rec {
+      name = "decimal";
+      version = "2.1.1";
+
+      src = fetchHex {
+        pkg = "decimal";
+        version = "${version}";
+        sha256 = "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc";
+      };
+
+      beamDeps = [];
+    };
+
+    deep_merge = buildMix rec {
+      name = "deep_merge";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "deep_merge";
+        version = "${version}";
+        sha256 = "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430";
+      };
+
+      beamDeps = [];
+    };
+
+    dialyxir = buildMix rec {
+      name = "dialyxir";
+      version = "1.4.3";
+
+      src = fetchHex {
+        pkg = "dialyxir";
+        version = "${version}";
+        sha256 = "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986";
+      };
+
+      beamDeps = [ erlex ];
+    };
+
+    earmark = buildMix rec {
+      name = "earmark";
+      version = "1.4.46";
+
+      src = fetchHex {
+        pkg = "earmark";
+        version = "${version}";
+        sha256 = "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a";
+      };
+
+      beamDeps = [];
+    };
+
+    earmark_parser = buildMix rec {
+      name = "earmark_parser";
+      version = "1.4.39";
+
+      src = fetchHex {
+        pkg = "earmark_parser";
+        version = "${version}";
+        sha256 = "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944";
+      };
+
+      beamDeps = [];
+    };
+
+    eblurhash = buildRebar3 rec {
+      name = "eblurhash";
+      version = "1.2.2";
+
+      src = fetchHex {
+        pkg = "eblurhash";
+        version = "${version}";
+        sha256 = "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c";
+      };
+
+      beamDeps = [];
+    };
+
+    ecto = buildMix rec {
+      name = "ecto";
+      version = "3.10.3";
+
+      src = fetchHex {
+        pkg = "ecto";
+        version = "${version}";
+        sha256 = "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433";
+      };
+
+      beamDeps = [ decimal jason telemetry ];
+    };
+
+    ecto_enum = buildMix rec {
+      name = "ecto_enum";
+      version = "1.4.0";
+
+      src = fetchHex {
+        pkg = "ecto_enum";
+        version = "${version}";
+        sha256 = "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4";
+      };
+
+      beamDeps = [ ecto ecto_sql postgrex ];
+    };
+
+    ecto_psql_extras = buildMix rec {
+      name = "ecto_psql_extras";
+      version = "0.7.15";
+
+      src = fetchHex {
+        pkg = "ecto_psql_extras";
+        version = "${version}";
+        sha256 = "b6127f3a5c6fc3d84895e4768cc7c199f22b48b67d6c99b13fbf4a374e73f039";
+      };
+
+      beamDeps = [ ecto_sql postgrex table_rex ];
+    };
+
+    ecto_sql = buildMix rec {
+      name = "ecto_sql";
+      version = "3.10.2";
+
+      src = fetchHex {
+        pkg = "ecto_sql";
+        version = "${version}";
+        sha256 = "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007";
+      };
+
+      beamDeps = [ db_connection ecto postgrex telemetry ];
+    };
+
+    elixir_make = buildMix rec {
+      name = "elixir_make";
+      version = "0.6.3";
+
+      src = fetchHex {
+        pkg = "elixir_make";
+        version = "${version}";
+        sha256 = "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716";
+      };
+
+      beamDeps = [];
+    };
+
+    erlex = buildMix rec {
+      name = "erlex";
+      version = "0.2.6";
+
+      src = fetchHex {
+        pkg = "erlex";
+        version = "${version}";
+        sha256 = "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75";
+      };
+
+      beamDeps = [];
+    };
+
+    eternal = buildMix rec {
+      name = "eternal";
+      version = "1.2.2";
+
+      src = fetchHex {
+        pkg = "eternal";
+        version = "${version}";
+        sha256 = "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782";
+      };
+
+      beamDeps = [];
+    };
+
+    ex_aws = buildMix rec {
+      name = "ex_aws";
+      version = "2.5.1";
+
+      src = fetchHex {
+        pkg = "ex_aws";
+        version = "${version}";
+        sha256 = "1b95431f70c446fa1871f0eb9b183043c5a625f75f9948a42d25f43ae2eff12b";
+      };
+
+      beamDeps = [ hackney jason mime sweet_xml telemetry ];
+    };
+
+    ex_aws_s3 = buildMix rec {
+      name = "ex_aws_s3";
+      version = "2.5.3";
+
+      src = fetchHex {
+        pkg = "ex_aws_s3";
+        version = "${version}";
+        sha256 = "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8";
+      };
+
+      beamDeps = [ ex_aws sweet_xml ];
+    };
+
+    ex_const = buildMix rec {
+      name = "ex_const";
+      version = "0.2.4";
+
+      src = fetchHex {
+        pkg = "ex_const";
+        version = "${version}";
+        sha256 = "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767";
+      };
+
+      beamDeps = [];
+    };
+
+    ex_doc = buildMix rec {
+      name = "ex_doc";
+      version = "0.31.1";
+
+      src = fetchHex {
+        pkg = "ex_doc";
+        version = "${version}";
+        sha256 = "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0";
+      };
+
+      beamDeps = [ earmark_parser makeup_elixir makeup_erlang ];
+    };
+
+    ex_machina = buildMix rec {
+      name = "ex_machina";
+      version = "2.7.0";
+
+      src = fetchHex {
+        pkg = "ex_machina";
+        version = "${version}";
+        sha256 = "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8";
+      };
+
+      beamDeps = [ ecto ecto_sql ];
+    };
+
+    ex_syslogger = buildMix rec {
+      name = "ex_syslogger";
+      version = "2.0.0";
+
+      src = fetchHex {
+        pkg = "ex_syslogger";
+        version = "${version}";
+        sha256 = "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e";
+      };
+
+      beamDeps = [ jason syslog ];
+    };
+
+    excoveralls = buildMix rec {
+      name = "excoveralls";
+      version = "0.16.1";
+
+      src = fetchHex {
+        pkg = "excoveralls";
+        version = "${version}";
+        sha256 = "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138";
+      };
+
+      beamDeps = [ hackney jason ];
+    };
+
+    expo = buildMix rec {
+      name = "expo";
+      version = "0.4.1";
+
+      src = fetchHex {
+        pkg = "expo";
+        version = "${version}";
+        sha256 = "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47";
+      };
+
+      beamDeps = [];
+    };
+
+    fast_html = buildMix rec {
+      name = "fast_html";
+      version = "2.3.0";
+
+      src = fetchHex {
+        pkg = "fast_html";
+        version = "${version}";
+        sha256 = "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d";
+      };
+
+      beamDeps = [ elixir_make nimble_pool ];
+    };
+
+    fast_sanitize = buildMix rec {
+      name = "fast_sanitize";
+      version = "0.2.3";
+
+      src = fetchHex {
+        pkg = "fast_sanitize";
+        version = "${version}";
+        sha256 = "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2";
+      };
+
+      beamDeps = [ fast_html plug ];
+    };
+
+    file_system = buildMix rec {
+      name = "file_system";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "file_system";
+        version = "${version}";
+        sha256 = "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d";
+      };
+
+      beamDeps = [];
+    };
+
+    finch = buildMix rec {
+      name = "finch";
+      version = "0.16.0";
+
+      src = fetchHex {
+        pkg = "finch";
+        version = "${version}";
+        sha256 = "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5";
+      };
+
+      beamDeps = [ castore mime mint nimble_options nimble_pool telemetry ];
+    };
+
+    flake_id = buildMix rec {
+      name = "flake_id";
+      version = "0.1.0";
+
+      src = fetchHex {
+        pkg = "flake_id";
+        version = "${version}";
+        sha256 = "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827";
+      };
+
+      beamDeps = [ base62 ecto ];
+    };
+
+    floki = buildMix rec {
+      name = "floki";
+      version = "0.35.3";
+
+      src = fetchHex {
+        pkg = "floki";
+        version = "${version}";
+        sha256 = "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4";
+      };
+
+      beamDeps = [];
+    };
+
+    gen_smtp = buildRebar3 rec {
+      name = "gen_smtp";
+      version = "1.2.0";
+
+      src = fetchHex {
+        pkg = "gen_smtp";
+        version = "${version}";
+        sha256 = "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779";
+      };
+
+      beamDeps = [ ranch ];
+    };
+
+    gettext = buildMix rec {
+      name = "gettext";
+      version = "0.22.3";
+
+      src = fetchHex {
+        pkg = "gettext";
+        version = "${version}";
+        sha256 = "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd";
+      };
+
+      beamDeps = [ expo ];
+    };
+
+    hackney = buildRebar3 rec {
+      name = "hackney";
+      version = "1.20.1";
+
+      src = fetchHex {
+        pkg = "hackney";
+        version = "${version}";
+        sha256 = "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3";
+      };
+
+      beamDeps = [ certifi idna metrics mimerl parse_trans ssl_verify_fun unicode_util_compat ];
+    };
+
+    hpax = buildMix rec {
+      name = "hpax";
+      version = "0.1.2";
+
+      src = fetchHex {
+        pkg = "hpax";
+        version = "${version}";
+        sha256 = "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13";
+      };
+
+      beamDeps = [];
+    };
+
+    html_entities = buildMix rec {
+      name = "html_entities";
+      version = "0.5.2";
+
+      src = fetchHex {
+        pkg = "html_entities";
+        version = "${version}";
+        sha256 = "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc";
+      };
+
+      beamDeps = [];
+    };
+
+    http_signatures = buildMix rec {
+      name = "http_signatures";
+      version = "0.1.2";
+
+      src = fetchHex {
+        pkg = "http_signatures";
+        version = "${version}";
+        sha256 = "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7";
+      };
+
+      beamDeps = [];
+    };
+
+    httpoison = buildMix rec {
+      name = "httpoison";
+      version = "1.8.2";
+
+      src = fetchHex {
+        pkg = "httpoison";
+        version = "${version}";
+        sha256 = "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921";
+      };
+
+      beamDeps = [ hackney ];
+    };
+
+    idna = buildRebar3 rec {
+      name = "idna";
+      version = "6.1.1";
+
+      src = fetchHex {
+        pkg = "idna";
+        version = "${version}";
+        sha256 = "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea";
+      };
+
+      beamDeps = [ unicode_util_compat ];
+    };
+
+    inet_cidr = buildMix rec {
+      name = "inet_cidr";
+      version = "1.0.8";
+
+      src = fetchHex {
+        pkg = "inet_cidr";
+        version = "${version}";
+        sha256 = "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e";
+      };
+
+      beamDeps = [];
+    };
+
+    jason = buildMix rec {
+      name = "jason";
+      version = "1.4.1";
+
+      src = fetchHex {
+        pkg = "jason";
+        version = "${version}";
+        sha256 = "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1";
+      };
+
+      beamDeps = [ decimal ];
+    };
+
+    joken = buildMix rec {
+      name = "joken";
+      version = "2.6.0";
+
+      src = fetchHex {
+        pkg = "joken";
+        version = "${version}";
+        sha256 = "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7";
+      };
+
+      beamDeps = [ jose ];
+    };
+
+    jose = buildMix rec {
+      name = "jose";
+      version = "1.11.6";
+
+      src = fetchHex {
+        pkg = "jose";
+        version = "${version}";
+        sha256 = "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738";
+      };
+
+      beamDeps = [];
+    };
+
+    jumper = buildMix rec {
+      name = "jumper";
+      version = "1.0.2";
+
+      src = fetchHex {
+        pkg = "jumper";
+        version = "${version}";
+        sha256 = "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1";
+      };
+
+      beamDeps = [];
+    };
+
+    mail = buildMix rec {
+      name = "mail";
+      version = "0.3.1";
+
+      src = fetchHex {
+        pkg = "mail";
+        version = "${version}";
+        sha256 = "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6";
+      };
+
+      beamDeps = [];
+    };
+
+    majic = buildMix rec {
+      name = "majic";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "majic";
+        version = "${version}";
+        sha256 = "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e";
+      };
+
+      beamDeps = [ elixir_make mime nimble_pool plug ];
+    };
+
+    makeup = buildMix rec {
+      name = "makeup";
+      version = "1.1.1";
+
+      src = fetchHex {
+        pkg = "makeup";
+        version = "${version}";
+        sha256 = "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48";
+      };
+
+      beamDeps = [ nimble_parsec ];
+    };
+
+    makeup_elixir = buildMix rec {
+      name = "makeup_elixir";
+      version = "0.16.1";
+
+      src = fetchHex {
+        pkg = "makeup_elixir";
+        version = "${version}";
+        sha256 = "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6";
+      };
+
+      beamDeps = [ makeup nimble_parsec ];
+    };
+
+    makeup_erlang = buildMix rec {
+      name = "makeup_erlang";
+      version = "0.1.4";
+
+      src = fetchHex {
+        pkg = "makeup_erlang";
+        version = "${version}";
+        sha256 = "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29";
+      };
+
+      beamDeps = [ makeup ];
+    };
+
+    meck = buildRebar3 rec {
+      name = "meck";
+      version = "0.9.2";
+
+      src = fetchHex {
+        pkg = "meck";
+        version = "${version}";
+        sha256 = "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826";
+      };
+
+      beamDeps = [];
+    };
+
+    metrics = buildRebar3 rec {
+      name = "metrics";
+      version = "1.0.1";
+
+      src = fetchHex {
+        pkg = "metrics";
+        version = "${version}";
+        sha256 = "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16";
+      };
+
+      beamDeps = [];
+    };
+
+    mime = buildMix rec {
+      name = "mime";
+      version = "1.6.0";
+
+      src = fetchHex {
+        pkg = "mime";
+        version = "${version}";
+        sha256 = "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7";
+      };
+
+      beamDeps = [];
+    };
+
+    mimerl = buildRebar3 rec {
+      name = "mimerl";
+      version = "1.2.0";
+
+      src = fetchHex {
+        pkg = "mimerl";
+        version = "${version}";
+        sha256 = "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323";
+      };
+
+      beamDeps = [];
+    };
+
+    mint = buildMix rec {
+      name = "mint";
+      version = "1.5.2";
+
+      src = fetchHex {
+        pkg = "mint";
+        version = "${version}";
+        sha256 = "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02";
+      };
+
+      beamDeps = [ castore hpax ];
+    };
+
+    mock = buildMix rec {
+      name = "mock";
+      version = "0.3.8";
+
+      src = fetchHex {
+        pkg = "mock";
+        version = "${version}";
+        sha256 = "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022";
+      };
+
+      beamDeps = [ meck ];
+    };
+
+    mogrify = buildMix rec {
+      name = "mogrify";
+      version = "0.9.3";
+
+      src = fetchHex {
+        pkg = "mogrify";
+        version = "${version}";
+        sha256 = "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6";
+      };
+
+      beamDeps = [];
+    };
+
+    mox = buildMix rec {
+      name = "mox";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "mox";
+        version = "${version}";
+        sha256 = "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13";
+      };
+
+      beamDeps = [];
+    };
+
+    nimble_options = buildMix rec {
+      name = "nimble_options";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "nimble_options";
+        version = "${version}";
+        sha256 = "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99";
+      };
+
+      beamDeps = [];
+    };
+
+    nimble_parsec = buildMix rec {
+      name = "nimble_parsec";
+      version = "1.4.0";
+
+      src = fetchHex {
+        pkg = "nimble_parsec";
+        version = "${version}";
+        sha256 = "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28";
+      };
+
+      beamDeps = [];
+    };
+
+    nimble_pool = buildMix rec {
+      name = "nimble_pool";
+      version = "0.2.6";
+
+      src = fetchHex {
+        pkg = "nimble_pool";
+        version = "${version}";
+        sha256 = "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f";
+      };
+
+      beamDeps = [];
+    };
+
+    oban = buildMix rec {
+      name = "oban";
+      version = "2.15.4";
+
+      src = fetchHex {
+        pkg = "oban";
+        version = "${version}";
+        sha256 = "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c";
+      };
+
+      beamDeps = [ ecto_sql jason postgrex telemetry ];
+    };
+
+    open_api_spex = buildMix rec {
+      name = "open_api_spex";
+      version = "3.18.2";
+
+      src = fetchHex {
+        pkg = "open_api_spex";
+        version = "${version}";
+        sha256 = "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b";
+      };
+
+      beamDeps = [ jason plug poison ];
+    };
+
+    parse_trans = buildRebar3 rec {
+      name = "parse_trans";
+      version = "3.4.1";
+
+      src = fetchHex {
+        pkg = "parse_trans";
+        version = "${version}";
+        sha256 = "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a";
+      };
+
+      beamDeps = [];
+    };
+
+    phoenix = buildMix rec {
+      name = "phoenix";
+      version = "1.7.11";
+
+      src = fetchHex {
+        pkg = "phoenix";
+        version = "${version}";
+        sha256 = "b1ec57f2e40316b306708fe59b92a16b9f6f4bf50ccfa41aa8c7feb79e0ec02a";
+      };
+
+      beamDeps = [ castore jason phoenix_pubsub phoenix_template phoenix_view plug plug_cowboy plug_crypto telemetry websock_adapter ];
+    };
+
+    phoenix_ecto = buildMix rec {
+      name = "phoenix_ecto";
+      version = "4.4.3";
+
+      src = fetchHex {
+        pkg = "phoenix_ecto";
+        version = "${version}";
+        sha256 = "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07";
+      };
+
+      beamDeps = [ ecto phoenix_html plug ];
+    };
+
+    phoenix_html = buildMix rec {
+      name = "phoenix_html";
+      version = "3.3.3";
+
+      src = fetchHex {
+        pkg = "phoenix_html";
+        version = "${version}";
+        sha256 = "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    phoenix_live_dashboard = buildMix rec {
+      name = "phoenix_live_dashboard";
+      version = "0.7.2";
+
+      src = fetchHex {
+        pkg = "phoenix_live_dashboard";
+        version = "${version}";
+        sha256 = "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7";
+      };
+
+      beamDeps = [ ecto ecto_psql_extras mime phoenix_live_view telemetry_metrics ];
+    };
+
+    phoenix_live_view = buildMix rec {
+      name = "phoenix_live_view";
+      version = "0.18.18";
+
+      src = fetchHex {
+        pkg = "phoenix_live_view";
+        version = "${version}";
+        sha256 = "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214";
+      };
+
+      beamDeps = [ jason phoenix phoenix_html phoenix_template phoenix_view telemetry ];
+    };
+
+    phoenix_pubsub = buildMix rec {
+      name = "phoenix_pubsub";
+      version = "2.1.3";
+
+      src = fetchHex {
+        pkg = "phoenix_pubsub";
+        version = "${version}";
+        sha256 = "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502";
+      };
+
+      beamDeps = [];
+    };
+
+    phoenix_swoosh = buildMix rec {
+      name = "phoenix_swoosh";
+      version = "1.2.1";
+
+      src = fetchHex {
+        pkg = "phoenix_swoosh";
+        version = "${version}";
+        sha256 = "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2";
+      };
+
+      beamDeps = [ finch hackney phoenix phoenix_html phoenix_view swoosh ];
+    };
+
+    phoenix_template = buildMix rec {
+      name = "phoenix_template";
+      version = "1.0.4";
+
+      src = fetchHex {
+        pkg = "phoenix_template";
+        version = "${version}";
+        sha256 = "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206";
+      };
+
+      beamDeps = [ phoenix_html ];
+    };
+
+    phoenix_view = buildMix rec {
+      name = "phoenix_view";
+      version = "2.0.3";
+
+      src = fetchHex {
+        pkg = "phoenix_view";
+        version = "${version}";
+        sha256 = "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64";
+      };
+
+      beamDeps = [ phoenix_html phoenix_template ];
+    };
+
+    plug = buildMix rec {
+      name = "plug";
+      version = "1.15.3";
+
+      src = fetchHex {
+        pkg = "plug";
+        version = "${version}";
+        sha256 = "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2";
+      };
+
+      beamDeps = [ mime plug_crypto telemetry ];
+    };
+
+    plug_cowboy = buildMix rec {
+      name = "plug_cowboy";
+      version = "2.7.0";
+
+      src = fetchHex {
+        pkg = "plug_cowboy";
+        version = "${version}";
+        sha256 = "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a";
+      };
+
+      beamDeps = [ cowboy cowboy_telemetry plug ];
+    };
+
+    plug_crypto = buildMix rec {
+      name = "plug_crypto";
+      version = "2.0.0";
+
+      src = fetchHex {
+        pkg = "plug_crypto";
+        version = "${version}";
+        sha256 = "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9";
+      };
+
+      beamDeps = [];
+    };
+
+    plug_static_index_html = buildMix rec {
+      name = "plug_static_index_html";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "plug_static_index_html";
+        version = "${version}";
+        sha256 = "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    poison = buildMix rec {
+      name = "poison";
+      version = "5.0.0";
+
+      src = fetchHex {
+        pkg = "poison";
+        version = "${version}";
+        sha256 = "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc";
+      };
+
+      beamDeps = [ decimal ];
+    };
+
+    poolboy = buildRebar3 rec {
+      name = "poolboy";
+      version = "1.5.2";
+
+      src = fetchHex {
+        pkg = "poolboy";
+        version = "${version}";
+        sha256 = "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3";
+      };
+
+      beamDeps = [];
+    };
+
+    postgrex = buildMix rec {
+      name = "postgrex";
+      version = "0.17.4";
+
+      src = fetchHex {
+        pkg = "postgrex";
+        version = "${version}";
+        sha256 = "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc";
+      };
+
+      beamDeps = [ db_connection decimal jason ];
+    };
+
+    pot = buildRebar3 rec {
+      name = "pot";
+      version = "1.0.2";
+
+      src = fetchHex {
+        pkg = "pot";
+        version = "${version}";
+        sha256 = "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0";
+      };
+
+      beamDeps = [];
+    };
+
+    ranch = buildRebar3 rec {
+      name = "ranch";
+      version = "1.8.0";
+
+      src = fetchHex {
+        pkg = "ranch";
+        version = "${version}";
+        sha256 = "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5";
+      };
+
+      beamDeps = [];
+    };
+
+    recon = buildMix rec {
+      name = "recon";
+      version = "2.5.4";
+
+      src = fetchHex {
+        pkg = "recon";
+        version = "${version}";
+        sha256 = "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263";
+      };
+
+      beamDeps = [];
+    };
+
+    remote_ip = buildMix rec {
+      name = "remote_ip";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "remote_ip";
+        version = "${version}";
+        sha256 = "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74";
+      };
+
+      beamDeps = [ combine plug ];
+    };
+
+    sleeplocks = buildRebar3 rec {
+      name = "sleeplocks";
+      version = "1.1.2";
+
+      src = fetchHex {
+        pkg = "sleeplocks";
+        version = "${version}";
+        sha256 = "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5";
+      };
+
+      beamDeps = [];
+    };
+
+    ssl_verify_fun = buildRebar3 rec {
+      name = "ssl_verify_fun";
+      version = "1.1.7";
+
+      src = fetchHex {
+        pkg = "ssl_verify_fun";
+        version = "${version}";
+        sha256 = "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8";
+      };
+
+      beamDeps = [];
+    };
+
+    statistex = buildMix rec {
+      name = "statistex";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "statistex";
+        version = "${version}";
+        sha256 = "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27";
+      };
+
+      beamDeps = [];
+    };
+
+    sweet_xml = buildMix rec {
+      name = "sweet_xml";
+      version = "0.7.4";
+
+      src = fetchHex {
+        pkg = "sweet_xml";
+        version = "${version}";
+        sha256 = "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167";
+      };
+
+      beamDeps = [];
+    };
+
+    swoosh = buildMix rec {
+      name = "swoosh";
+      version = "1.15.2";
+
+      src = fetchHex {
+        pkg = "swoosh";
+        version = "${version}";
+        sha256 = "9f7739c02f6c7c0ca82ee397f3bfe0465dbe4c8a65372ac2a5584bf147dd5831";
+      };
+
+      beamDeps = [ cowboy ex_aws finch gen_smtp hackney jason mail mime plug plug_cowboy telemetry ];
+    };
+
+    syslog = buildRebar3 rec {
+      name = "syslog";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "syslog";
+        version = "${version}";
+        sha256 = "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1";
+      };
+
+      beamDeps = [];
+    };
+
+    table_rex = buildMix rec {
+      name = "table_rex";
+      version = "4.0.0";
+
+      src = fetchHex {
+        pkg = "table_rex";
+        version = "${version}";
+        sha256 = "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55";
+      };
+
+      beamDeps = [];
+    };
+
+    telemetry = buildRebar3 rec {
+      name = "telemetry";
+      version = "1.2.1";
+
+      src = fetchHex {
+        pkg = "telemetry";
+        version = "${version}";
+        sha256 = "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5";
+      };
+
+      beamDeps = [];
+    };
+
+    telemetry_metrics = buildMix rec {
+      name = "telemetry_metrics";
+      version = "0.6.2";
+
+      src = fetchHex {
+        pkg = "telemetry_metrics";
+        version = "${version}";
+        sha256 = "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561";
+      };
+
+      beamDeps = [ telemetry ];
+    };
+
+    telemetry_metrics_prometheus = buildMix rec {
+      name = "telemetry_metrics_prometheus";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "telemetry_metrics_prometheus";
+        version = "${version}";
+        sha256 = "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26";
+      };
+
+      beamDeps = [ plug_cowboy telemetry_metrics_prometheus_core ];
+    };
+
+    telemetry_metrics_prometheus_core = buildMix rec {
+      name = "telemetry_metrics_prometheus_core";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "telemetry_metrics_prometheus_core";
+        version = "${version}";
+        sha256 = "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069";
+      };
+
+      beamDeps = [ telemetry telemetry_metrics ];
+    };
+
+    telemetry_poller = buildRebar3 rec {
+      name = "telemetry_poller";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "telemetry_poller";
+        version = "${version}";
+        sha256 = "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e";
+      };
+
+      beamDeps = [ telemetry ];
+    };
+
+    tesla = buildMix rec {
+      name = "tesla";
+      version = "1.8.0";
+
+      src = fetchHex {
+        pkg = "tesla";
+        version = "${version}";
+        sha256 = "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f";
+      };
+
+      beamDeps = [ castore finch hackney jason mime mint poison telemetry ];
+    };
+
+    timex = buildMix rec {
+      name = "timex";
+      version = "3.7.11";
+
+      src = fetchHex {
+        pkg = "timex";
+        version = "${version}";
+        sha256 = "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa";
+      };
+
+      beamDeps = [ combine gettext tzdata ];
+    };
+
+    trailing_format_plug = buildMix rec {
+      name = "trailing_format_plug";
+      version = "0.0.7";
+
+      src = fetchHex {
+        pkg = "trailing_format_plug";
+        version = "${version}";
+        sha256 = "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    tzdata = buildMix rec {
+      name = "tzdata";
+      version = "1.1.1";
+
+      src = fetchHex {
+        pkg = "tzdata";
+        version = "${version}";
+        sha256 = "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787";
+      };
+
+      beamDeps = [ hackney ];
+    };
+
+    ueberauth = buildMix rec {
+      name = "ueberauth";
+      version = "0.10.5";
+
+      src = fetchHex {
+        pkg = "ueberauth";
+        version = "${version}";
+        sha256 = "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    unicode_util_compat = buildRebar3 rec {
+      name = "unicode_util_compat";
+      version = "0.7.0";
+
+      src = fetchHex {
+        pkg = "unicode_util_compat";
+        version = "${version}";
+        sha256 = "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521";
+      };
+
+      beamDeps = [];
+    };
+
+    unsafe = buildMix rec {
+      name = "unsafe";
+      version = "1.0.2";
+
+      src = fetchHex {
+        pkg = "unsafe";
+        version = "${version}";
+        sha256 = "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675";
+      };
+
+      beamDeps = [];
+    };
+
+    vex = buildMix rec {
+      name = "vex";
+      version = "0.9.2";
+
+      src = fetchHex {
+        pkg = "vex";
+        version = "${version}";
+        sha256 = "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb";
+      };
+
+      beamDeps = [];
+    };
+
+    web_push_encryption = buildMix rec {
+      name = "web_push_encryption";
+      version = "0.3.1";
+
+      src = fetchHex {
+        pkg = "web_push_encryption";
+        version = "${version}";
+        sha256 = "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2";
+      };
+
+      beamDeps = [ httpoison jose ];
+    };
+
+    websock = buildMix rec {
+      name = "websock";
+      version = "0.5.3";
+
+      src = fetchHex {
+        pkg = "websock";
+        version = "${version}";
+        sha256 = "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453";
+      };
+
+      beamDeps = [];
+    };
+
+    websock_adapter = buildMix rec {
+      name = "websock_adapter";
+      version = "0.5.5";
+
+      src = fetchHex {
+        pkg = "websock_adapter";
+        version = "${version}";
+        sha256 = "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9";
+      };
+
+      beamDeps = [ plug plug_cowboy websock ];
+    };
+
+    websockex = buildMix rec {
+      name = "websockex";
+      version = "0.4.3";
+
+      src = fetchHex {
+        pkg = "websockex";
+        version = "${version}";
+        sha256 = "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4";
+      };
+
+      beamDeps = [];
+    };
+  };
+in self
+
diff --git a/pkgs/default.nix b/pkgs/default.nix
new file mode 100644
index 0000000..a0f2d96
--- /dev/null
+++ b/pkgs/default.nix
@@ -0,0 +1,5 @@
+{ pkgs, ... }: let
+  callPackage = pkg: pkgs.callPackage pkg;
+in {
+  akkoma = callPackage ./akkoma { };
+}