Music streaming on ATProto!

Compare changes

Choose any two refs to compare.

Changed files
+4534 -5390
.vscode
apps
assets
config
lexicons
lib
packages
priv
gettext
en
LC_MESSAGES
repo
static
test
+6
.formatter.exs
··· 1 + [ 2 + import_deps: [:ecto, :ecto_sql, :phoenix, :hologram], 3 + subdirectories: ["priv/*/migrations"], 4 + plugins: [Phoenix.LiveView.HTMLFormatter], 5 + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"] 6 + ]
+14 -12
.gitignore
··· 1 1 node_modules 2 - 3 - # Output 4 2 .output 5 3 .vercel 6 4 .netlify ··· 8 6 .svelte-kit 9 7 build 10 8 .elixir_ls 11 - 12 - # OS 13 9 .DS_Store 14 10 Thumbs.db 15 - 16 - # Env 17 11 .env 18 12 .env.* 19 13 !.env.example 20 14 !.env.test 21 15 .envrc 22 16 .direnv 23 - 24 - # Vite 25 17 vite.config.js.timestamp-* 26 18 vite.config.ts.timestamp-* 27 - 28 - # Nix 29 19 result 20 + /_build/ 21 + /cover/ 22 + /deps/ 23 + /doc/ 24 + /.fetch 25 + erl_crash.dump 26 + *.ez 27 + /tmp/ 28 + comet-*.tar 29 + /priv/static/assets/ 30 + /priv/static/hologram/ 31 + /priv/static/cache_manifest.json 32 + npm-debug.log 33 + /assets/node_modules/ 30 34 31 - # Dumps 32 - erl_crash.dump
+2 -10
.prettierrc
··· 1 1 { 2 - "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 2 + "plugins": ["prettier-plugin-tailwindcss"], 3 3 "proseWrap": "always", 4 - "htmlWhitespaceSensitivity": "ignore", 5 - "overrides": [ 6 - { 7 - "files": "*.svelte", 8 - "options": { 9 - "parser": "svelte" 10 - } 11 - } 12 - ] 4 + "htmlWhitespaceSensitivity": "ignore" 13 5 }
+5 -8
.vscode/settings.json
··· 1 1 { 2 2 "json.schemas": [ 3 3 { 4 - "fileMatch": ["/packages/lexicons/*/**/*.json"], 4 + "fileMatch": ["/lexicons/*/**/*.json"], 5 5 "url": "https://gist.githubusercontent.com/mary-ext/6e428031c18799d1587048b456d118cb/raw/4322c492384ac5da33986dee9588938a88d922f1/schema.json" 6 6 } 7 7 ], 8 - "elixirLS.projectDir": "./apps/backend", 9 - "search.exclude": { 10 - "**/node_modules": true, 11 - "**/bower_components": true, 12 - "**/*.code-search": true, 13 - "packages/lexicons/src/**/*": true 14 - } 8 + "files.associations": { 9 + "*.holo": "phoenix-heex" 10 + }, 11 + 15 12 }
+449
AGENTS.md
··· 1 + This is a web application written using the Phoenix web framework. 2 + 3 + ## Project guidelines 4 + 5 + - Use `mix precommit` alias when you are done with all changes and fix any pending issues 6 + - Use the already included and available `:req` (`Req`) library for HTTP requests, **avoid** `:httpoison`, `:tesla`, and `:httpc`. Req is included by default and is the preferred HTTP client for Phoenix apps 7 + 8 + ### Phoenix v1.8 guidelines 9 + 10 + - **Always** begin your LiveView templates with `<Layouts.app flash={@flash} ...>` which wraps all inner content 11 + - The `MyAppWeb.Layouts` module is aliased in the `my_app_web.ex` file, so you can use it without needing to alias it again 12 + - Anytime you run into errors with no `current_scope` assign: 13 + - You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `<Layouts.app>` 14 + - **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed 15 + - Phoenix v1.8 moved the `<.flash_group>` component to the `Layouts` module. You are **forbidden** from calling `<.flash_group>` outside of the `layouts.ex` module 16 + - Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar 17 + - **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors 18 + - If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your 19 + custom classes must fully style the input 20 + 21 + ### JS and CSS guidelines 22 + 23 + - **Use Tailwind CSS classes and custom CSS rules** to create polished, responsive, and visually stunning interfaces. 24 + - Tailwindcss v4 **no longer needs a tailwind.config.js** and uses a new import syntax in `app.css`: 25 + 26 + @import "tailwindcss" source(none); 27 + @source "../css"; 28 + @source "../js"; 29 + @source "../../lib/my_app_web"; 30 + 31 + - **Always use and maintain this import syntax** in the app.css file for projects generated with `phx.new` 32 + - **Never** use `@apply` when writing raw css 33 + - **Always** manually write your own tailwind-based components instead of using daisyUI for a unique, world-class design 34 + - Out of the box **only the app.js and app.css bundles are supported** 35 + - You cannot reference an external vendor'd script `src` or link `href` in the layouts 36 + - You must import the vendor deps into app.js and app.css to use them 37 + - **Never write inline <script>custom js</script> tags within templates** 38 + 39 + ### UI/UX & design guidelines 40 + 41 + - **Produce world-class UI designs** with a focus on usability, aesthetics, and modern design principles 42 + - Implement **subtle micro-interactions** (e.g., button hover effects, and smooth transitions) 43 + - Ensure **clean typography, spacing, and layout balance** for a refined, premium look 44 + - Focus on **delightful details** like hover effects, loading states, and smooth page transitions 45 + 46 + 47 + <!-- usage-rules-start --> 48 + 49 + <!-- phoenix:elixir-start --> 50 + ## Elixir guidelines 51 + 52 + - Elixir lists **do not support index based access via the access syntax** 53 + 54 + **Never do this (invalid)**: 55 + 56 + i = 0 57 + mylist = ["blue", "green"] 58 + mylist[i] 59 + 60 + Instead, **always** use `Enum.at`, pattern matching, or `List` for index based list access, ie: 61 + 62 + i = 0 63 + mylist = ["blue", "green"] 64 + Enum.at(mylist, i) 65 + 66 + - Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc 67 + you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie: 68 + 69 + # INVALID: we are rebinding inside the `if` and the result never gets assigned 70 + if connected?(socket) do 71 + socket = assign(socket, :val, val) 72 + end 73 + 74 + # VALID: we rebind the result of the `if` to a new variable 75 + socket = 76 + if connected?(socket) do 77 + assign(socket, :val, val) 78 + end 79 + 80 + - **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors 81 + - **Never** use map access syntax (`changeset[:field]`) on structs as they do not implement the Access behaviour by default. For regular structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist, `Ecto.Changeset.get_field/2` for changesets 82 + - Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package) 83 + - Don't use `String.to_atom/1` on user input (memory leak risk) 84 + - Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards 85 + - Elixir's builtin OTP primitives like `DynamicSupervisor` and `Registry`, require names in the child spec, such as `{DynamicSupervisor, name: MyApp.MyDynamicSup}`, then you can use `DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)` 86 + - Use `Task.async_stream(collection, callback, options)` for concurrent enumeration with back-pressure. The majority of times you will want to pass `timeout: :infinity` as option 87 + 88 + ## Mix guidelines 89 + 90 + - Read the docs and options before using tasks (by using `mix help task_name`) 91 + - To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed` 92 + - `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason 93 + 94 + ## Test guidelines 95 + 96 + - **Always use `start_supervised!/1`** to start processes in tests as it guarantees cleanup between tests 97 + - **Avoid** `Process.sleep/1` and `Process.alive?/1` in tests 98 + - Instead of sleeping to wait for a process to finish, **always** use `Process.monitor/1` and assert on the DOWN message: 99 + 100 + ref = Process.monitor(pid) 101 + assert_receive {:DOWN, ^ref, :process, ^pid, :normal} 102 + 103 + - Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages 104 + <!-- phoenix:elixir-end --> 105 + 106 + <!-- phoenix:phoenix-start --> 107 + ## Phoenix guidelines 108 + 109 + - Remember Phoenix router `scope` blocks include an optional alias which is prefixed for all routes within the scope. **Always** be mindful of this when creating routes within a scope to avoid duplicate module prefixes. 110 + 111 + - You **never** need to create your own `alias` for route definitions! The `scope` provides the alias, ie: 112 + 113 + scope "/admin", AppWeb.Admin do 114 + pipe_through :browser 115 + 116 + live "/users", UserLive, :index 117 + end 118 + 119 + the UserLive route would point to the `AppWeb.Admin.UserLive` module 120 + 121 + - `Phoenix.View` no longer is needed or included with Phoenix, don't use it 122 + <!-- phoenix:phoenix-end --> 123 + 124 + <!-- phoenix:ecto-start --> 125 + ## Ecto Guidelines 126 + 127 + - **Always** preload Ecto associations in queries when they'll be accessed in templates, ie a message that needs to reference the `message.user.email` 128 + - Remember `import Ecto.Query` and other supporting modules when you write `seeds.exs` 129 + - `Ecto.Schema` fields always use the `:string` type, even for `:text`, columns, ie: `field :name, :string` 130 + - `Ecto.Changeset.validate_number/2` **DOES NOT SUPPORT the `:allow_nil` option**. By default, Ecto validations only run if a change for the given field exists and the change value is not nil, so such as option is never needed 131 + - You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields 132 + - Fields which are set programatically, such as `user_id`, must not be listed in `cast` calls or similar for security purposes. Instead they must be explicitly set when creating the struct 133 + - **Always** invoke `mix ecto.gen.migration migration_name_using_underscores` when generating migration files, so the correct timestamp and conventions are applied 134 + <!-- phoenix:ecto-end --> 135 + 136 + <!-- phoenix:html-start --> 137 + ## Phoenix HTML guidelines 138 + 139 + - Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E` 140 + - **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1` function to build forms. **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` as they are outdated 141 + - When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(socket, form: to_form(...))` and `<.form for={@form} id="msg-form">`), then access those forms in the template via `@form[:field]` 142 + - **Always** add unique DOM IDs to key elements (like forms, buttons, etc) when writing templates, these IDs can later be used in tests (`<.form for={@form} id="product-form">`) 143 + - For "app wide" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all LiveViews, LiveComponent's, and all modules that do `use MyAppWeb, :html` (replace "my_app" by the actual app name) 144 + 145 + - Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`. **Never use `else if` or `elseif` in Elixir**, **always** use `cond` or `case` for multiple conditionals. 146 + 147 + **Never do this (invalid)**: 148 + 149 + <%= if condition do %> 150 + ... 151 + <% else if other_condition %> 152 + ... 153 + <% end %> 154 + 155 + Instead **always** do this: 156 + 157 + <%= cond do %> 158 + <% condition -> %> 159 + ... 160 + <% condition2 -> %> 161 + ... 162 + <% true -> %> 163 + ... 164 + <% end %> 165 + 166 + - HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `<pre>` or `<code>` block you *must* annotate the parent tag with `phx-no-curly-interpolation`: 167 + 168 + <code phx-no-curly-interpolation> 169 + let obj = {key: "val"} 170 + </code> 171 + 172 + Within `phx-no-curly-interpolation` annotated tags, you can use `{` and `}` without escaping them, and dynamic Elixir expressions can still be used with `<%= ... %>` syntax 173 + 174 + - HEEx class attrs support lists, but you must **always** use list `[...]` syntax. You can use the class list syntax to conditionally add classes, **always do this for multiple class values**: 175 + 176 + <a class={[ 177 + "px-2 text-white", 178 + @some_flag && "py-5", 179 + if(@other_condition, do: "border-red-500", else: "border-blue-100"), 180 + ... 181 + ]}>Text</a> 182 + 183 + and **always** wrap `if`'s inside `{...}` expressions with parens, like done above (`if(@other_condition, do: "...", else: "...")`) 184 + 185 + and **never** do this, since it's invalid (note the missing `[` and `]`): 186 + 187 + <a class={ 188 + "px-2 text-white", 189 + @some_flag && "py-5" 190 + }> ... 191 + => Raises compile syntax error on invalid HEEx attr syntax 192 + 193 + - **Never** use `<% Enum.each %>` or non-for comprehensions for generating template content, instead **always** use `<%= for item <- @collection do %>` 194 + - HEEx HTML comments use `<%!-- comment --%>`. **Always** use the HEEx HTML comment syntax for template comments (`<%!-- comment --%>`) 195 + - HEEx allows interpolation via `{...}` and `<%= ... %>`, but the `<%= %>` **only** works within tag bodies. **Always** use the `{...}` syntax for interpolation within tag attributes, and for interpolation of values within tag bodies. **Always** interpolate block constructs (if, cond, case, for) within tag bodies using `<%= ... %>`. 196 + 197 + **Always** do this: 198 + 199 + <div id={@id}> 200 + {@my_assign} 201 + <%= if @some_block_condition do %> 202 + {@another_assign} 203 + <% end %> 204 + </div> 205 + 206 + and **Never** do this โ€“ the program will terminate with a syntax error: 207 + 208 + <%!-- THIS IS INVALID NEVER EVER DO THIS --%> 209 + <div id="<%= @invalid_interpolation %>"> 210 + {if @invalid_block_construct do} 211 + {end} 212 + </div> 213 + <!-- phoenix:html-end --> 214 + 215 + <!-- phoenix:liveview-start --> 216 + ## Phoenix LiveView guidelines 217 + 218 + - **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews 219 + - **Avoid LiveComponent's** unless you have a strong, specific need for them 220 + - LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive` 221 + 222 + ### LiveView streams 223 + 224 + - **Always** use LiveView streams for collections for assigning regular lists to avoid memory ballooning and runtime termination with the following operations: 225 + - basic append of N items - `stream(socket, :messages, [new_msg])` 226 + - resetting stream with new items - `stream(socket, :messages, [new_msg], reset: true)` (e.g. for filtering items) 227 + - prepend to stream - `stream(socket, :messages, [new_msg], at: -1)` 228 + - deleting items - `stream_delete(socket, :messages, msg)` 229 + 230 + - When using the `stream/3` interfaces in the LiveView, the LiveView template must 1) always set `phx-update="stream"` on the parent element, with a DOM id on the parent element like `id="messages"` and 2) consume the `@streams.stream_name` collection and use the id as the DOM id for each child. For a call like `stream(socket, :messages, [new_msg])` in the LiveView, the template would be: 231 + 232 + <div id="messages" phx-update="stream"> 233 + <div :for={{id, msg} <- @streams.messages} id={id}> 234 + {msg.text} 235 + </div> 236 + </div> 237 + 238 + - LiveView streams are *not* enumerable, so you cannot use `Enum.filter/2` or `Enum.reject/2` on them. Instead, if you want to filter, prune, or refresh a list of items on the UI, you **must refetch the data and re-stream the entire stream collection, passing reset: true**: 239 + 240 + def handle_event("filter", %{"filter" => filter}, socket) do 241 + # re-fetch the messages based on the filter 242 + messages = list_messages(filter) 243 + 244 + {:noreply, 245 + socket 246 + |> assign(:messages_empty?, messages == []) 247 + # reset the stream with the new messages 248 + |> stream(:messages, messages, reset: true)} 249 + end 250 + 251 + - LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes: 252 + 253 + <div id="tasks" phx-update="stream"> 254 + <div class="hidden only:block">No tasks yet</div> 255 + <div :for={{id, task} <- @stream.tasks} id={id}> 256 + {task.name} 257 + </div> 258 + </div> 259 + 260 + The above only works if the empty state is the only HTML block alongside the stream for-comprehension. 261 + 262 + - When updating an assign that should change content inside any streamed item(s), you MUST re-stream the items 263 + along with the updated assign: 264 + 265 + def handle_event("edit_message", %{"message_id" => message_id}, socket) do 266 + message = Chat.get_message!(message_id) 267 + edit_form = to_form(Chat.change_message(message, %{content: message.content})) 268 + 269 + # re-insert message so @editing_message_id toggle logic takes effect for that stream item 270 + {:noreply, 271 + socket 272 + |> stream_insert(:messages, message) 273 + |> assign(:editing_message_id, String.to_integer(message_id)) 274 + |> assign(:edit_form, edit_form)} 275 + end 276 + 277 + And in the template: 278 + 279 + <div id="messages" phx-update="stream"> 280 + <div :for={{id, message} <- @streams.messages} id={id} class="flex group"> 281 + {message.username} 282 + <%= if @editing_message_id == message.id do %> 283 + <%!-- Edit mode --%> 284 + <.form for={@edit_form} id="edit-form-#{message.id}" phx-submit="save_edit"> 285 + ... 286 + </.form> 287 + <% end %> 288 + </div> 289 + </div> 290 + 291 + - **Never** use the deprecated `phx-update="append"` or `phx-update="prepend"` for collections 292 + 293 + ### LiveView JavaScript interop 294 + 295 + - Remember anytime you use `phx-hook="MyHook"` and that JS hook manages its own DOM, you **must** also set the `phx-update="ignore"` attribute 296 + - **Always** provide an unique DOM id alongside `phx-hook` otherwise a compiler error will be raised 297 + 298 + LiveView hooks come in two flavors, 1) colocated js hooks for "inline" scripts defined inside HEEx, 299 + and 2) external `phx-hook` annotations where JavaScript object literals are defined and passed to the `LiveSocket` constructor. 300 + 301 + #### Inline colocated js hooks 302 + 303 + **Never** write raw embedded `<script>` tags in heex as they are incompatible with LiveView. 304 + Instead, **always use a colocated js hook script tag (`:type={Phoenix.LiveView.ColocatedHook}`) 305 + when writing scripts inside the template**: 306 + 307 + <input type="text" name="user[phone_number]" id="user-phone-number" phx-hook=".PhoneNumber" /> 308 + <script :type={Phoenix.LiveView.ColocatedHook} name=".PhoneNumber"> 309 + export default { 310 + mounted() { 311 + this.el.addEventListener("input", e => { 312 + let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/) 313 + if(match) { 314 + this.el.value = `${match[1]}-${match[2]}-${match[3]}` 315 + } 316 + }) 317 + } 318 + } 319 + </script> 320 + 321 + - colocated hooks are automatically integrated into the app.js bundle 322 + - colocated hooks names **MUST ALWAYS** start with a `.` prefix, i.e. `.PhoneNumber` 323 + 324 + #### External phx-hook 325 + 326 + External JS hooks (`<div id="myhook" phx-hook="MyHook">`) must be placed in `assets/js/` and passed to the 327 + LiveSocket constructor: 328 + 329 + const MyHook = { 330 + mounted() { ... } 331 + } 332 + let liveSocket = new LiveSocket("/live", Socket, { 333 + hooks: { MyHook } 334 + }); 335 + 336 + #### Pushing events between client and server 337 + 338 + Use LiveView's `push_event/3` when you need to push events/data to the client for a phx-hook to handle. 339 + **Always** return or rebind the socket on `push_event/3` when pushing events: 340 + 341 + # re-bind socket so we maintain event state to be pushed 342 + socket = push_event(socket, "my_event", %{...}) 343 + 344 + # or return the modified socket directly: 345 + def handle_event("some_event", _, socket) do 346 + {:noreply, push_event(socket, "my_event", %{...})} 347 + end 348 + 349 + Pushed events can then be picked up in a JS hook with `this.handleEvent`: 350 + 351 + mounted() { 352 + this.handleEvent("my_event", data => console.log("from server:", data)); 353 + } 354 + 355 + Clients can also push an event to the server and receive a reply with `this.pushEvent`: 356 + 357 + mounted() { 358 + this.el.addEventListener("click", e => { 359 + this.pushEvent("my_event", { one: 1 }, reply => console.log("got reply from server:", reply)); 360 + }) 361 + } 362 + 363 + Where the server handled it via: 364 + 365 + def handle_event("my_event", %{"one" => 1}, socket) do 366 + {:reply, %{two: 2}, socket} 367 + end 368 + 369 + ### LiveView tests 370 + 371 + - `Phoenix.LiveViewTest` module and `LazyHTML` (included) for making your assertions 372 + - Form tests are driven by `Phoenix.LiveViewTest`'s `render_submit/2` and `render_change/2` functions 373 + - Come up with a step-by-step test plan that splits major test cases into small, isolated files. You may start with simpler tests that verify content exists, gradually add interaction tests 374 + - **Always reference the key element IDs you added in the LiveView templates in your tests** for `Phoenix.LiveViewTest` functions like `element/2`, `has_element/2`, selectors, etc 375 + - **Never** tests again raw HTML, **always** use `element/2`, `has_element/2`, and similar: `assert has_element?(view, "#my-form")` 376 + - Instead of relying on testing text content, which can change, favor testing for the presence of key elements 377 + - Focus on testing outcomes rather than implementation details 378 + - Be aware that `Phoenix.Component` functions like `<.form>` might produce different HTML than expected. Test against the output HTML structure, not your mental model of what you expect it to be 379 + - When facing test failures with element selectors, add debug statements to print the actual HTML, but use `LazyHTML` selectors to limit the output, ie: 380 + 381 + html = render(view) 382 + document = LazyHTML.from_fragment(html) 383 + matches = LazyHTML.filter(document, "your-complex-selector") 384 + IO.inspect(matches, label: "Matches") 385 + 386 + ### Form handling 387 + 388 + #### Creating a form from params 389 + 390 + If you want to create a form based on `handle_event` params: 391 + 392 + def handle_event("submitted", params, socket) do 393 + {:noreply, assign(socket, form: to_form(params))} 394 + end 395 + 396 + When you pass a map to `to_form/1`, it assumes said map contains the form params, which are expected to have string keys. 397 + 398 + You can also specify a name to nest the params: 399 + 400 + def handle_event("submitted", %{"user" => user_params}, socket) do 401 + {:noreply, assign(socket, form: to_form(user_params, as: :user))} 402 + end 403 + 404 + #### Creating a form from changesets 405 + 406 + When using changesets, the underlying data, form params, and errors are retrieved from it. The `:as` option is automatically computed too. E.g. if you have a user schema: 407 + 408 + defmodule MyApp.Users.User do 409 + use Ecto.Schema 410 + ... 411 + end 412 + 413 + And then you create a changeset that you pass to `to_form`: 414 + 415 + %MyApp.Users.User{} 416 + |> Ecto.Changeset.change() 417 + |> to_form() 418 + 419 + Once the form is submitted, the params will be available under `%{"user" => user_params}`. 420 + 421 + In the template, the form form assign can be passed to the `<.form>` function component: 422 + 423 + <.form for={@form} id="todo-form" phx-change="validate" phx-submit="save"> 424 + <.input field={@form[:field]} type="text" /> 425 + </.form> 426 + 427 + Always give the form an explicit, unique DOM ID, like `id="todo-form"`. 428 + 429 + #### Avoiding form errors 430 + 431 + **Always** use a form assigned via `to_form/2` in the LiveView, and the `<.input>` component in the template. In the template **always access forms this**: 432 + 433 + <%!-- ALWAYS do this (valid) --%> 434 + <.form for={@form} id="my-form"> 435 + <.input field={@form[:field]} type="text" /> 436 + </.form> 437 + 438 + And **never** do this: 439 + 440 + <%!-- NEVER do this (invalid) --%> 441 + <.form for={@changeset} id="my-form"> 442 + <.input field={@changeset[:field]} type="text" /> 443 + </.form> 444 + 445 + - You are FORBIDDEN from accessing the changeset in the template as it will cause errors 446 + - **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/2` assigned in the LiveView module that is derived from a changeset 447 + <!-- phoenix:liveview-end --> 448 + 449 + <!-- usage-rules-end -->
-5
apps/backend/.formatter.exs
··· 1 - [ 2 - import_deps: [:ecto, :ecto_sql, :phoenix], 3 - subdirectories: ["priv/*/migrations"], 4 - inputs: ["*.{ex,exs}", "{config,lib,test,priv}/**/*.{ex,exs}", "priv/*/seeds.exs"] 5 - ]
-27
apps/backend/.gitignore
··· 1 - # The directory Mix will write compiled artifacts to. 2 - /_build/ 3 - 4 - # If you run "mix test --cover", coverage assets end up here. 5 - /cover/ 6 - 7 - # The directory Mix downloads your dependencies sources to. 8 - /deps/ 9 - 10 - # Where 3rd-party dependencies like ExDoc output generated docs. 11 - /doc/ 12 - 13 - # Ignore .fetch files in case you like to edit your project deps locally. 14 - /.fetch 15 - 16 - # If the VM crashes, it generates a dump, let's ignore it too. 17 - erl_crash.dump 18 - 19 - # Also ignore archive artifacts (built via "mix archive.build"). 20 - *.ez 21 - 22 - # Temporary files, for example, from tests. 23 - /tmp/ 24 - 25 - # Ignore package tarball (built via "mix hex.build"). 26 - comet-*.tar 27 -
-24
apps/backend/README.md
··· 1 - # Comet AppView 2 - 3 - [Phoenix](https://www.phoenixframework.org)-powered AppView for Comet. 4 - 5 - --- 6 - 7 - To start your Phoenix server: 8 - 9 - - Run `mix setup` to install and setup dependencies 10 - - Start Phoenix endpoint with `mix phx.server` or inside IEx with 11 - `iex -S mix phx.server` 12 - 13 - Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 14 - 15 - Ready to run in production? Please 16 - [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). 17 - 18 - ## Learn more 19 - 20 - - Official website: https://www.phoenixframework.org/ 21 - - Guides: https://hexdocs.pm/phoenix/overview.html 22 - - Docs: https://hexdocs.pm/phoenix 23 - - Forum: https://elixirforum.com/c/phoenix-forum 24 - - Source: https://github.com/phoenixframework/phoenix
-37
apps/backend/config/config.exs
··· 1 - # This file is responsible for configuring your application 2 - # and its dependencies with the aid of the Config module. 3 - # 4 - # This configuration file is loaded before any dependency and 5 - # is restricted to this project. 6 - 7 - # General application configuration 8 - import Config 9 - 10 - config :comet, 11 - ecto_repos: [Comet.Repo], 12 - generators: [timestamp_type: :utc_datetime, binary_id: true] 13 - 14 - config :comet, Comet.Repo, migration_primary_key: [name: :id, type: :binary_id] 15 - 16 - # Configures the endpoint 17 - config :comet, CometWeb.Endpoint, 18 - url: [host: "localhost"], 19 - adapter: Bandit.PhoenixAdapter, 20 - render_errors: [ 21 - formats: [json: CometWeb.ErrorJSON], 22 - layout: false 23 - ], 24 - pubsub_server: Comet.PubSub, 25 - live_view: [signing_salt: "oq2xYeBj"] 26 - 27 - # Configures Elixir's Logger 28 - config :logger, :console, 29 - format: "$time $metadata[$level] $message\n", 30 - metadata: [:request_id] 31 - 32 - # Use Jason for JSON parsing in Phoenix 33 - config :phoenix, :json_library, Jason 34 - 35 - # Import environment specific config. This must remain at the bottom 36 - # of this file so it overrides the configuration defined above. 37 - import_config "#{config_env()}.exs"
-63
apps/backend/config/dev.exs
··· 1 - import Config 2 - 3 - # Configure your database 4 - config :comet, Comet.Repo, 5 - username: "comet", 6 - password: "comet", 7 - hostname: "localhost", 8 - database: "comet_dev", 9 - stacktrace: true, 10 - show_sensitive_data_on_connection_error: true, 11 - pool_size: 10 12 - 13 - # For development, we disable any cache and enable 14 - # debugging and code reloading. 15 - # 16 - # The watchers configuration can be used to run external 17 - # watchers to your application. For example, we can use it 18 - # to bundle .js and .css sources. 19 - config :comet, CometWeb.Endpoint, 20 - # Binding to loopback ipv4 address prevents access from other machines. 21 - # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 22 - http: [ip: {127, 0, 0, 1}, port: 4000], 23 - check_origin: false, 24 - code_reloader: true, 25 - debug_errors: true, 26 - secret_key_base: "Vw9UaVO8YBKiooaOlZ2Rhx7xJHydL9s2YIviOwiiQz8Cy24+mLBB3Fj+9jvOIdQE", 27 - watchers: [] 28 - 29 - # ## SSL Support 30 - # 31 - # In order to use HTTPS in development, a self-signed 32 - # certificate can be generated by running the following 33 - # Mix task: 34 - # 35 - # mix phx.gen.cert 36 - # 37 - # Run `mix help phx.gen.cert` for more information. 38 - # 39 - # The `http:` config above can be replaced with: 40 - # 41 - # https: [ 42 - # port: 4001, 43 - # cipher_suite: :strong, 44 - # keyfile: "priv/cert/selfsigned_key.pem", 45 - # certfile: "priv/cert/selfsigned.pem" 46 - # ], 47 - # 48 - # If desired, both `http:` and `https:` keys can be 49 - # configured to run both http and https servers on 50 - # different ports. 51 - 52 - # Enable dev routes for dashboard and mailbox 53 - config :comet, dev_routes: true 54 - 55 - # Do not include metadata nor timestamps in development logs 56 - config :logger, :console, format: "[$level] $message\n" 57 - 58 - # Set a higher stacktrace during development. Avoid configuring such 59 - # in production as building large stacktraces may be expensive. 60 - config :phoenix, :stacktrace_depth, 20 61 - 62 - # Initialize plugs at runtime for faster development compilation 63 - config :phoenix, :plug_init_mode, :runtime
-7
apps/backend/config/prod.exs
··· 1 - import Config 2 - 3 - # Do not print debug messages in production 4 - config :logger, level: :info 5 - 6 - # Runtime production configuration, including reading 7 - # of environment variables, is done on config/runtime.exs.
-99
apps/backend/config/runtime.exs
··· 1 - import Config 2 - 3 - # config/runtime.exs is executed for all environments, including 4 - # during releases. It is executed after compilation and before the 5 - # system starts, so it is typically used to load production configuration 6 - # and secrets from environment variables or elsewhere. Do not define 7 - # any compile-time configuration in here, as it won't be applied. 8 - # The block below contains prod specific runtime configuration. 9 - 10 - # ## Using releases 11 - # 12 - # If you use `mix release`, you need to explicitly enable the server 13 - # by passing the PHX_SERVER=true when you start it: 14 - # 15 - # PHX_SERVER=true bin/comet start 16 - # 17 - # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 - # script that automatically sets the env var above. 19 - if System.get_env("PHX_SERVER") do 20 - config :comet, CometWeb.Endpoint, server: true 21 - end 22 - 23 - if config_env() == :prod do 24 - database_url = 25 - System.get_env("DATABASE_URL") || 26 - raise """ 27 - environment variable DATABASE_URL is missing. 28 - For example: ecto://USER:PASS@HOST/DATABASE 29 - """ 30 - 31 - maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: [] 32 - 33 - config :comet, Comet.Repo, 34 - # ssl: true, 35 - url: database_url, 36 - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), 37 - socket_options: maybe_ipv6 38 - 39 - # The secret key base is used to sign/encrypt cookies and other secrets. 40 - # A default value is used in config/dev.exs and config/test.exs but you 41 - # want to use a different value for prod and you most likely don't want 42 - # to check this value into version control, so we use an environment 43 - # variable instead. 44 - secret_key_base = 45 - System.get_env("SECRET_KEY_BASE") || 46 - raise """ 47 - environment variable SECRET_KEY_BASE is missing. 48 - You can generate one by calling: mix phx.gen.secret 49 - """ 50 - 51 - host = System.get_env("PHX_HOST") || "example.com" 52 - port = String.to_integer(System.get_env("PORT") || "4000") 53 - 54 - config :comet, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") 55 - 56 - config :comet, CometWeb.Endpoint, 57 - url: [host: host, port: 443, scheme: "https"], 58 - http: [ 59 - # Enable IPv6 and bind on all interfaces. 60 - # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 61 - # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 62 - # for details about using IPv6 vs IPv4 and loopback vs public addresses. 63 - ip: {0, 0, 0, 0, 0, 0, 0, 0}, 64 - port: port 65 - ], 66 - secret_key_base: secret_key_base 67 - 68 - # ## SSL Support 69 - # 70 - # To get SSL working, you will need to add the `https` key 71 - # to your endpoint configuration: 72 - # 73 - # config :comet, CometWeb.Endpoint, 74 - # https: [ 75 - # ..., 76 - # port: 443, 77 - # cipher_suite: :strong, 78 - # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 79 - # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 80 - # ] 81 - # 82 - # The `cipher_suite` is set to `:strong` to support only the 83 - # latest and more secure SSL ciphers. This means old browsers 84 - # and clients may not be supported. You can set it to 85 - # `:compatible` for wider support. 86 - # 87 - # `:keyfile` and `:certfile` expect an absolute path to the key 88 - # and cert in disk or a relative path inside priv, for example 89 - # "priv/ssl/server.key". For all supported SSL configuration 90 - # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 91 - # 92 - # We also recommend setting `force_ssl` in your config/prod.exs, 93 - # ensuring no data is ever sent via http, always redirecting to https: 94 - # 95 - # config :comet, CometWeb.Endpoint, 96 - # force_ssl: [hsts: true] 97 - # 98 - # Check `Plug.SSL` for all available options in `force_ssl`. 99 - end
-27
apps/backend/config/test.exs
··· 1 - import Config 2 - 3 - # Configure your database 4 - # 5 - # The MIX_TEST_PARTITION environment variable can be used 6 - # to provide built-in test partitioning in CI environment. 7 - # Run `mix help test` for more information. 8 - config :comet, Comet.Repo, 9 - username: "postgres", 10 - password: "postgres", 11 - hostname: "localhost", 12 - database: "comet_test#{System.get_env("MIX_TEST_PARTITION")}", 13 - pool: Ecto.Adapters.SQL.Sandbox, 14 - pool_size: System.schedulers_online() * 2 15 - 16 - # We don't run a server during test. If one is required, 17 - # you can enable the server option below. 18 - config :comet, CometWeb.Endpoint, 19 - http: [ip: {127, 0, 0, 1}, port: 4002], 20 - secret_key_base: "eaG5CrPmVserxnUlu8DyG8I6i3m3TBDOi8fsKn2niwYUMhjps0YkWWMGRnoSXvGf", 21 - server: false 22 - 23 - # Print only warnings and errors during test 24 - config :logger, level: :warning 25 - 26 - # Initialize plugs at runtime for faster test compilation 27 - config :phoenix, :plug_init_mode, :runtime
-173
apps/backend/lib/atproto/atproto.ex
··· 1 - # AUTOGENERATED: This file was generated using the mix task `lexgen`. 2 - defmodule Atproto do 3 - @default_pds_hostname Application.compile_env( 4 - :atproto, 5 - :default_pds_hostname, 6 - "https://bsky.social" 7 - ) 8 - 9 - @typedoc """ 10 - A type representing the names of the options that can be passed to `query/3` and `procedure/3`. 11 - """ 12 - @type xrpc_opt :: :pds_hostname | :authorization 13 - 14 - @typedoc """ 15 - A keyword list of options that can be passed to `query/3` and `procedure/3`. 16 - """ 17 - @type xrpc_opts :: [{xrpc_opt(), any()}] 18 - 19 - @doc """ 20 - Converts a JSON string, or decoded JSON map, into a struct based on the given module. 21 - 22 - This function uses `String.to_existing_atom/1` to convert the keys of the map to atoms, meaning this will throw an error if the input JSON contains keys which are not already defined as atoms in the existing structs or codebase. 23 - """ 24 - @spec decode_to_struct(module(), binary() | map()) :: map() 25 - def decode_to_struct(module, json) when is_binary(json) do 26 - decode_to_struct(module, Jason.decode!(json, keys: :atoms!)) 27 - end 28 - 29 - def decode_to_struct(module, map) when is_map(map) do 30 - Map.merge(module.new(), map) 31 - end 32 - 33 - @doc """ 34 - Raises an error if any required parameters are missing from the given map. 35 - """ 36 - @spec ensure_required(map(), [String.t()]) :: map() 37 - def ensure_required(params, required) do 38 - if Enum.all?(required, fn key -> Map.has_key?(params, key) end) do 39 - params 40 - else 41 - raise ArgumentError, "Missing one or more required parameters: #{Enum.join(required, ", ")}" 42 - end 43 - end 44 - 45 - @doc """ 46 - Executes a "GET" HTTP request and returns the response body as a map. 47 - 48 - If the `:pds_hostname` option is not provided, the default PDS hostname as provided in the compile-time configuration will be used. 49 - """ 50 - @spec query(map(), String.t(), xrpc_opts()) :: Req.Request.t() 51 - def query(params, target, opts \\ []) do 52 - target 53 - |> endpoint(opts) 54 - |> URI.new!() 55 - |> URI.append_query(URI.encode_query(params)) 56 - |> Req.get(build_req_auth(opts)) 57 - |> handle_response(opts) 58 - end 59 - 60 - @doc """ 61 - Executes a "POST" HTTP request and returns the response body as a map. 62 - 63 - If the `:pds_hostname` option is not provided, the default PDS hostname as provided in the compile-time configuration will be used. 64 - """ 65 - @spec procedure(map(), String.t(), xrpc_opts()) :: {:ok | :refresh | :error, map()} 66 - def procedure(params, target, opts \\ []) do 67 - req_opts = 68 - opts 69 - |> build_req_auth() 70 - |> build_req_headers(opts, target) 71 - |> build_req_body(params, target) 72 - 73 - target 74 - |> endpoint(opts) 75 - |> URI.new!() 76 - |> Req.post(req_opts) 77 - |> handle_response(opts) 78 - end 79 - 80 - defp build_req_auth(opts) do 81 - case Keyword.get(opts, :access_token) do 82 - nil -> 83 - case Keyword.get(opts, :admin_token) do 84 - nil -> 85 - [] 86 - 87 - token -> 88 - [auth: {:basic, "admin:#{token}"}] 89 - end 90 - 91 - token -> 92 - [auth: {:bearer, token}] 93 - end 94 - end 95 - 96 - defp build_req_headers(req_opts, opts, "com.atproto.repo.uploadBlob") do 97 - [ 98 - {:headers, 99 - [ 100 - {"Content-Type", Keyword.fetch!(opts, :content_type)}, 101 - {"Content-Length", Keyword.fetch!(opts, :content_length)} 102 - ]} 103 - | req_opts 104 - ] 105 - end 106 - 107 - defp build_req_headers(req_opts, _opts, _target), do: req_opts 108 - 109 - defp build_req_body(opts, blob, "com.atproto.repo.uploadBlob") do 110 - [{:body, blob} | opts] 111 - end 112 - 113 - defp build_req_body(opts, %{} = params, _target) when map_size(params) > 0 do 114 - [{:json, params} | opts] 115 - end 116 - 117 - defp build_req_body(opts, _params, _target), do: opts 118 - 119 - defp endpoint(target, opts) do 120 - (Keyword.get(opts, :pds_hostname) || @default_pds_hostname) <> "/xrpc/" <> target 121 - end 122 - 123 - defp handle_response({:ok, %Req.Response{} = response}, opts) do 124 - case response.status do 125 - x when x in 200..299 -> 126 - {:ok, response.body} 127 - 128 - _ -> 129 - if response.body["error"] == "ExpiredToken" do 130 - {:ok, user} = 131 - Com.Atproto.Server.RefreshSession.main(%{}, 132 - access_token: Keyword.get(opts, :refresh_token) 133 - ) 134 - 135 - {:refresh, user} 136 - else 137 - {:error, response.body} 138 - end 139 - end 140 - end 141 - 142 - defp handle_response(error, _opts), do: error 143 - 144 - @doc """ 145 - Converts a "map-like" entity into a standard map. This will also omit any entries that have a `nil` value. 146 - 147 - This is useful for converting structs or schemas into regular maps before sending them over XRPC requests. 148 - 149 - You may optionally pass in an keyword list of options: 150 - 151 - - `:stringify` - `boolean` - If `true`, converts the keys to strings. Otherwise, converts keys to atoms. Default is `false`. 152 - - *Note*: When `false`, this feature uses the `to_existing_atom/1` function to avoid reckless conversion of string keys. 153 - """ 154 - @spec to_map(map() | struct()) :: map() 155 - def to_map(%{__struct__: _} = m, opts \\ []) do 156 - string_keys = Keyword.get(opts, :stringify, false) 157 - 158 - m 159 - |> Map.drop([:__struct__, :__meta__]) 160 - |> Enum.map(fn 161 - {_, nil} -> 162 - nil 163 - 164 - {k, v} when is_atom(k) -> 165 - if string_keys, do: {to_string(k), v}, else: {k, v} 166 - 167 - {k, v} when is_binary(k) -> 168 - if string_keys, do: {k, v}, else: {String.to_existing_atom(k), v} 169 - end) 170 - |> Enum.reject(&is_nil/1) 171 - |> Enum.into(%{}) 172 - end 173 - end
-15
apps/backend/lib/atproto/sh/comet/v0/actor/getProfile/xrpc.ex
··· 1 - defmodule Sh.Comet.V0.Actor.GetProfile do 2 - 3 - @doc """ 4 - Get the profile view of an actor. 5 - """ 6 - @spec main(%{ 7 - actor: String.t() 8 - }, Atproto.xrpc_opts()) :: {:ok, Sh.Comet.V0.Actor.Profile.View.t()} | {:error, any} 9 - def main(params \\ %{}, opts \\ []) do 10 - params 11 - |> Map.take([:actor]) 12 - |> Atproto.ensure_required([:actor]) 13 - |> Atproto.query("sh.comet.v0.actor.getProfile", opts) 14 - end 15 - end
-15
apps/backend/lib/atproto/sh/comet/v0/actor/getProfiles/xrpc.ex
··· 1 - defmodule Sh.Comet.V0.Actor.GetProfiles do 2 - 3 - @doc """ 4 - Get the profile views of multiple actors. 5 - """ 6 - @spec main(%{ 7 - actors: list(String.t()) 8 - }, Atproto.xrpc_opts()) :: {:ok, %{profiles: list(Sh.Comet.V0.Actor.Profile.View.t())}} | {:error, any} 9 - def main(params \\ %{}, opts \\ []) do 10 - params 11 - |> Map.take([:actors]) 12 - |> Atproto.ensure_required([:actors]) 13 - |> Atproto.query("sh.comet.v0.actor.getProfiles", opts) 14 - end 15 - end
-30
apps/backend/lib/atproto/sh/comet/v0/actor/profile/schema.ex
··· 1 - defmodule Sh.Comet.V0.Actor.Profile do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - A user's Comet profile. 7 - """ 8 - @primary_key {:id, :binary_id, autogenerate: false} 9 - schema "sh.comet.v0.actor.profile" do 10 - field :avatar, :map 11 - field :banner, :map 12 - field :createdAt, :utc_datetime 13 - field :description, :string 14 - field :descriptionFacets, :map 15 - field :displayName, :string 16 - field :featuredItems, {:array, :string} 17 - 18 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 19 - # Ensure that you do not change this field via manual manipulation or changeset operations. 20 - field :"$type", :string, default: "sh.comet.v0.actor.profile" 21 - end 22 - 23 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 24 - 25 - def changeset(struct, params \\ %{}) do 26 - struct 27 - |> cast(params, [:avatar, :banner, :createdAt, :description, :descriptionFacets, :displayName, :featuredItems]) 28 - |> validate_length(:featuredItems, max: 5) 29 - end 30 - end
-104
apps/backend/lib/atproto/sh/comet/v0/actor/profile/structs.ex
··· 1 - 2 - defmodule Sh.Comet.V0.Actor.Profile.ViewerState do 3 - @moduledoc """ 4 - Metadata about the requesting account's relationship with the user. TODO: determine if we create our own graph or inherit bsky's. 5 - """ 6 - 7 - @derive Jason.Encoder 8 - defstruct [ 9 - 10 - ] 11 - 12 - @type t() :: %__MODULE__{ 13 - 14 - } 15 - 16 - @spec new() :: t() 17 - def new(), do: %__MODULE__{} 18 - 19 - @spec from(binary() | map()) :: t() 20 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 21 - end 22 - 23 - defmodule Sh.Comet.V0.Actor.Profile.ViewFull do 24 - @moduledoc """ 25 - 26 - """ 27 - 28 - @derive Jason.Encoder 29 - defstruct [ 30 - avatar: nil, 31 - banner: nil, 32 - createdAt: nil, 33 - description: nil, 34 - descriptionFacets: nil, 35 - did: nil, 36 - displayName: nil, 37 - featuredItems: [], 38 - followersCount: 0, 39 - followsCount: 0, 40 - handle: nil, 41 - indexedAt: nil, 42 - playlistsCount: 0, 43 - tracksCount: 0, 44 - viewer: nil 45 - ] 46 - 47 - @type t() :: %__MODULE__{ 48 - avatar: String.t(), 49 - banner: String.t(), 50 - createdAt: DateTime.t(), 51 - description: String.t(), 52 - descriptionFacets: Sh.Comet.V0.Richtext.Facet.Main.t(), 53 - did: String.t(), 54 - displayName: String.t(), 55 - featuredItems: list(String.t()), 56 - followersCount: integer, 57 - followsCount: integer, 58 - handle: String.t(), 59 - indexedAt: DateTime.t(), 60 - playlistsCount: integer, 61 - tracksCount: integer, 62 - viewer: Sh.Comet.V0.Actor.Profile.ViewerState.t() 63 - } 64 - 65 - @spec new() :: t() 66 - def new(), do: %__MODULE__{} 67 - 68 - @spec from(binary() | map()) :: t() 69 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 70 - end 71 - 72 - defmodule Sh.Comet.V0.Actor.Profile.View do 73 - @moduledoc """ 74 - 75 - """ 76 - 77 - @derive Jason.Encoder 78 - defstruct [ 79 - avatar: nil, 80 - createdAt: nil, 81 - did: nil, 82 - displayName: nil, 83 - handle: nil, 84 - indexedAt: nil, 85 - viewer: nil 86 - ] 87 - 88 - @type t() :: %__MODULE__{ 89 - avatar: String.t(), 90 - createdAt: DateTime.t(), 91 - did: String.t(), 92 - displayName: String.t(), 93 - handle: String.t(), 94 - indexedAt: DateTime.t(), 95 - viewer: Sh.Comet.V0.Actor.Profile.ViewerState.t() 96 - } 97 - 98 - @spec new() :: t() 99 - def new(), do: %__MODULE__{} 100 - 101 - @spec from(binary() | map()) :: t() 102 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 103 - end 104 -
-30
apps/backend/lib/atproto/sh/comet/v0/feed/comment/schema.ex
··· 1 - defmodule Sh.Comet.V0.Feed.Comment do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - A comment on a piece of Comet media. 7 - """ 8 - @primary_key {:id, :id, autogenerate: false} 9 - schema "sh.comet.v0.feed.comment" do 10 - field :createdAt, :utc_datetime 11 - field :facets, {:array, :map} 12 - field :langs, {:array, :string} 13 - field :reply, :string 14 - field :subject, :string 15 - field :text, :string 16 - 17 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 18 - # Ensure that you do not change this field via manual manipulation or changeset operations. 19 - field :"$type", :string, default: "sh.comet.v0.feed.comment" 20 - end 21 - 22 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 23 - 24 - def changeset(struct, params \\ %{}) do 25 - struct 26 - |> cast(params, [:createdAt, :facets, :langs, :reply, :subject, :text]) 27 - |> validate_required([:createdAt, :subject, :text]) 28 - |> validate_length(:langs, max: 3) 29 - end 30 - end
-49
apps/backend/lib/atproto/sh/comet/v0/feed/defs/structs.ex
··· 1 - 2 - defmodule Sh.Comet.V0.Feed.Defs.ViewerState do 3 - @moduledoc """ 4 - Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. 5 - """ 6 - 7 - @derive Jason.Encoder 8 - defstruct [ 9 - featured: false, 10 - like: nil, 11 - repost: nil 12 - ] 13 - 14 - @type t() :: %__MODULE__{ 15 - featured: boolean, 16 - like: String.t(), 17 - repost: String.t() 18 - } 19 - 20 - @spec new() :: t() 21 - def new(), do: %__MODULE__{} 22 - 23 - @spec from(binary() | map()) :: t() 24 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 25 - end 26 - 27 - defmodule Sh.Comet.V0.Feed.Defs.Link do 28 - @moduledoc """ 29 - Link for the track. Usually to acquire it in some way, e.g. via free download or purchase. | TODO: multiple links? 30 - """ 31 - 32 - @derive Jason.Encoder 33 - defstruct [ 34 - type: nil, 35 - value: nil 36 - ] 37 - 38 - @type t() :: %__MODULE__{ 39 - type: String.t(), 40 - value: String.t() 41 - } 42 - 43 - @spec new() :: t() 44 - def new(), do: %__MODULE__{} 45 - 46 - @spec from(binary() | map()) :: t() 47 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 48 - end 49 -
-17
apps/backend/lib/atproto/sh/comet/v0/feed/getActorPlaylists/xrpc.ex
··· 1 - defmodule Sh.Comet.V0.Feed.GetActorPlaylists do 2 - 3 - @doc """ 4 - Get a list of an actor's playlists. 5 - """ 6 - @spec main(%{ 7 - actor: String.t(), 8 - cursor: String.t(), 9 - limit: integer 10 - }, Atproto.xrpc_opts()) :: {:ok, %{cursor: String.t(), playlists: list(Sh.Comet.V0.Feed.Playlist.View.t())}} | {:error, any} 11 - def main(params \\ %{}, opts \\ []) do 12 - params 13 - |> Map.take([:actor, :cursor, :limit]) 14 - |> Atproto.ensure_required([:actor]) 15 - |> Atproto.query("sh.comet.v0.feed.getActorPlaylists", opts) 16 - end 17 - end
-17
apps/backend/lib/atproto/sh/comet/v0/feed/getActorTracks/xrpc.ex
··· 1 - defmodule Sh.Comet.V0.Feed.GetActorTracks do 2 - 3 - @doc """ 4 - Get a list of an actor's tracks. 5 - """ 6 - @spec main(%{ 7 - actor: String.t(), 8 - cursor: String.t(), 9 - limit: integer 10 - }, Atproto.xrpc_opts()) :: {:ok, %{cursor: String.t(), tracks: list(Sh.Comet.V0.Feed.Track.View.t())}} | {:error, any} 11 - def main(params \\ %{}, opts \\ []) do 12 - params 13 - |> Map.take([:actor, :cursor, :limit]) 14 - |> Atproto.ensure_required([:actor]) 15 - |> Atproto.query("sh.comet.v0.feed.getActorTracks", opts) 16 - end 17 - end
-25
apps/backend/lib/atproto/sh/comet/v0/feed/like/schema.ex
··· 1 - defmodule Sh.Comet.V0.Feed.Like do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - Record representing a 'like' of some media. Weakly linked with just an at-uri. 7 - """ 8 - @primary_key {:id, :id, autogenerate: false} 9 - schema "sh.comet.v0.feed.like" do 10 - field :createdAt, :utc_datetime 11 - field :subject, :string 12 - 13 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 14 - # Ensure that you do not change this field via manual manipulation or changeset operations. 15 - field :"$type", :string, default: "sh.comet.v0.feed.like" 16 - end 17 - 18 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 19 - 20 - def changeset(struct, params \\ %{}) do 21 - struct 22 - |> cast(params, [:createdAt, :subject]) 23 - |> validate_required([:createdAt, :subject]) 24 - end 25 - end
-25
apps/backend/lib/atproto/sh/comet/v0/feed/play/schema.ex
··· 1 - defmodule Sh.Comet.V0.Feed.Play do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - Record representing a 'play' of some media. 7 - """ 8 - @primary_key {:id, :id, autogenerate: false} 9 - schema "sh.comet.v0.feed.play" do 10 - field :createdAt, :utc_datetime 11 - field :subject, :string 12 - 13 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 14 - # Ensure that you do not change this field via manual manipulation or changeset operations. 15 - field :"$type", :string, default: "sh.comet.v0.feed.play" 16 - end 17 - 18 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 19 - 20 - def changeset(struct, params \\ %{}) do 21 - struct 22 - |> cast(params, [:createdAt, :subject]) 23 - |> validate_required([:createdAt, :subject]) 24 - end 25 - end
-32
apps/backend/lib/atproto/sh/comet/v0/feed/playlist/schema.ex
··· 1 - defmodule Sh.Comet.V0.Feed.Playlist do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - A Comet playlist, containing many audio tracks. 7 - """ 8 - @primary_key {:id, :id, autogenerate: false} 9 - schema "sh.comet.v0.feed.playlist" do 10 - field :createdAt, :utc_datetime 11 - field :description, :string 12 - field :descriptionFacets, :map 13 - field :image, :map 14 - field :link, :map 15 - field :tags, {:array, :string} 16 - field :title, :string 17 - field :type, :string 18 - 19 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 20 - # Ensure that you do not change this field via manual manipulation or changeset operations. 21 - field :"$type", :string, default: "sh.comet.v0.feed.playlist" 22 - end 23 - 24 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 25 - 26 - def changeset(struct, params \\ %{}) do 27 - struct 28 - |> cast(params, [:createdAt, :description, :descriptionFacets, :image, :link, :tags, :title, :type]) 29 - |> validate_required([:createdAt, :title, :type]) 30 - |> validate_length(:tags, max: 8) 31 - end 32 - end
-44
apps/backend/lib/atproto/sh/comet/v0/feed/playlist/structs.ex
··· 1 - 2 - defmodule Sh.Comet.V0.Feed.Playlist.View do 3 - @moduledoc """ 4 - 5 - """ 6 - 7 - @derive Jason.Encoder 8 - defstruct [ 9 - author: nil, 10 - cid: nil, 11 - commentCount: 0, 12 - image: nil, 13 - indexedAt: nil, 14 - likeCount: 0, 15 - record: nil, 16 - repostCount: 0, 17 - trackCount: 0, 18 - tracks: [], 19 - uri: nil, 20 - viewer: nil 21 - ] 22 - 23 - @type t() :: %__MODULE__{ 24 - author: Sh.Comet.V0.Actor.Profile.ViewFull.t(), 25 - cid: String.t(), 26 - commentCount: integer, 27 - image: String.t(), 28 - indexedAt: DateTime.t(), 29 - likeCount: integer, 30 - record: Sh.Comet.V0.Feed.Playlist.Main.t(), 31 - repostCount: integer, 32 - trackCount: integer, 33 - tracks: list(Sh.Comet.V0.Feed.Track.View.t()), 34 - uri: String.t(), 35 - viewer: Sh.Comet.V0.Feed.Defs.ViewerState.t() 36 - } 37 - 38 - @spec new() :: t() 39 - def new(), do: %__MODULE__{} 40 - 41 - @spec from(binary() | map()) :: t() 42 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 43 - end 44 -
-27
apps/backend/lib/atproto/sh/comet/v0/feed/playlistTrack/schema.ex
··· 1 - defmodule Sh.Comet.V0.Feed.PlaylistTrack do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - A link between a Comet track and a playlist. 7 - """ 8 - @primary_key {:id, :id, autogenerate: false} 9 - schema "sh.comet.v0.feed.playlistTrack" do 10 - field :playlist, :string 11 - field :position, :integer 12 - field :track, :string 13 - 14 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 15 - # Ensure that you do not change this field via manual manipulation or changeset operations. 16 - field :"$type", :string, default: "sh.comet.v0.feed.playlistTrack" 17 - end 18 - 19 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 20 - 21 - def changeset(struct, params \\ %{}) do 22 - struct 23 - |> cast(params, [:playlist, :position, :track]) 24 - |> validate_required([:playlist, :position, :track]) 25 - |> validate_length(:position, min: 0) 26 - end 27 - end
-25
apps/backend/lib/atproto/sh/comet/v0/feed/repost/schema.ex
··· 1 - defmodule Sh.Comet.V0.Feed.Repost do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - Record representing a 'repost' of some media. Weakly linked with just an at-uri. 7 - """ 8 - @primary_key {:id, :id, autogenerate: false} 9 - schema "sh.comet.v0.feed.repost" do 10 - field :createdAt, :utc_datetime 11 - field :subject, :string 12 - 13 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 14 - # Ensure that you do not change this field via manual manipulation or changeset operations. 15 - field :"$type", :string, default: "sh.comet.v0.feed.repost" 16 - end 17 - 18 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 19 - 20 - def changeset(struct, params \\ %{}) do 21 - struct 22 - |> cast(params, [:createdAt, :subject]) 23 - |> validate_required([:createdAt, :subject]) 24 - end 25 - end
-32
apps/backend/lib/atproto/sh/comet/v0/feed/track/schema.ex
··· 1 - defmodule Sh.Comet.V0.Feed.Track do 2 - use Ecto.Schema 3 - import Ecto.Changeset 4 - 5 - @doc """ 6 - A Comet audio track. TODO: should probably have some sort of pre-calculated waveform, or have a query to get one from a blob? 7 - """ 8 - @primary_key {:id, :id, autogenerate: false} 9 - schema "sh.comet.v0.feed.track" do 10 - field :audio, :map 11 - field :createdAt, :utc_datetime 12 - field :description, :string 13 - field :descriptionFacets, :map 14 - field :image, :map 15 - field :link, :map 16 - field :tags, {:array, :string} 17 - field :title, :string 18 - 19 - # DO NOT CHANGE! This field is required for all records and must be set to the NSID of the lexicon. 20 - # Ensure that you do not change this field via manual manipulation or changeset operations. 21 - field :"$type", :string, default: "sh.comet.v0.feed.track" 22 - end 23 - 24 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 25 - 26 - def changeset(struct, params \\ %{}) do 27 - struct 28 - |> cast(params, [:audio, :createdAt, :description, :descriptionFacets, :image, :link, :tags, :title]) 29 - |> validate_required([:audio, :createdAt, :title]) 30 - |> validate_length(:tags, max: 8) 31 - end 32 - end
-44
apps/backend/lib/atproto/sh/comet/v0/feed/track/structs.ex
··· 1 - 2 - defmodule Sh.Comet.V0.Feed.Track.View do 3 - @moduledoc """ 4 - 5 - """ 6 - 7 - @derive Jason.Encoder 8 - defstruct [ 9 - audio: nil, 10 - author: nil, 11 - cid: nil, 12 - commentCount: 0, 13 - image: nil, 14 - indexedAt: nil, 15 - likeCount: 0, 16 - playCount: 0, 17 - record: nil, 18 - repostCount: 0, 19 - uri: nil, 20 - viewer: nil 21 - ] 22 - 23 - @type t() :: %__MODULE__{ 24 - audio: String.t(), 25 - author: Sh.Comet.V0.Actor.Profile.ViewFull.t(), 26 - cid: String.t(), 27 - commentCount: integer, 28 - image: String.t(), 29 - indexedAt: DateTime.t(), 30 - likeCount: integer, 31 - playCount: integer, 32 - record: Sh.Comet.V0.Feed.Track.Main.t(), 33 - repostCount: integer, 34 - uri: String.t(), 35 - viewer: Sh.Comet.V0.Feed.Defs.ViewerState.t() 36 - } 37 - 38 - @spec new() :: t() 39 - def new(), do: %__MODULE__{} 40 - 41 - @spec from(binary() | map()) :: t() 42 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 43 - end 44 -
-131
apps/backend/lib/atproto/sh/comet/v0/richtext/facet/structs.ex
··· 1 - 2 - defmodule Sh.Comet.V0.Richtext.Facet.Timestamp do 3 - @moduledoc """ 4 - Facet feature for a timestamp in a track. The text usually is in the format of 'hh:mm:ss' with the hour section being omitted if unnecessary. 5 - """ 6 - 7 - @derive Jason.Encoder 8 - defstruct [ 9 - timestamp: 0 10 - ] 11 - 12 - @type t() :: %__MODULE__{ 13 - timestamp: integer 14 - } 15 - 16 - @spec new() :: t() 17 - def new(), do: %__MODULE__{} 18 - 19 - @spec from(binary() | map()) :: t() 20 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 21 - end 22 - 23 - defmodule Sh.Comet.V0.Richtext.Facet.Tag do 24 - @moduledoc """ 25 - Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). 26 - """ 27 - 28 - @derive Jason.Encoder 29 - defstruct [ 30 - tag: nil 31 - ] 32 - 33 - @type t() :: %__MODULE__{ 34 - tag: String.t() 35 - } 36 - 37 - @spec new() :: t() 38 - def new(), do: %__MODULE__{} 39 - 40 - @spec from(binary() | map()) :: t() 41 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 42 - end 43 - 44 - defmodule Sh.Comet.V0.Richtext.Facet.Mention do 45 - @moduledoc """ 46 - Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. 47 - """ 48 - 49 - @derive Jason.Encoder 50 - defstruct [ 51 - did: nil 52 - ] 53 - 54 - @type t() :: %__MODULE__{ 55 - did: String.t() 56 - } 57 - 58 - @spec new() :: t() 59 - def new(), do: %__MODULE__{} 60 - 61 - @spec from(binary() | map()) :: t() 62 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 63 - end 64 - 65 - defmodule Sh.Comet.V0.Richtext.Facet.Main do 66 - @moduledoc """ 67 - Annotation of a sub-string within rich text. 68 - """ 69 - 70 - @derive Jason.Encoder 71 - defstruct [ 72 - features: [], 73 - index: nil 74 - ] 75 - 76 - @type t() :: %__MODULE__{ 77 - features: list(any), 78 - index: Sh.Comet.V0.Richtext.Facet.ByteSlice.t() 79 - } 80 - 81 - @spec new() :: t() 82 - def new(), do: %__MODULE__{} 83 - 84 - @spec from(binary() | map()) :: t() 85 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 86 - end 87 - 88 - defmodule Sh.Comet.V0.Richtext.Facet.Link do 89 - @moduledoc """ 90 - Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. 91 - """ 92 - 93 - @derive Jason.Encoder 94 - defstruct [ 95 - uri: nil 96 - ] 97 - 98 - @type t() :: %__MODULE__{ 99 - uri: String.t() 100 - } 101 - 102 - @spec new() :: t() 103 - def new(), do: %__MODULE__{} 104 - 105 - @spec from(binary() | map()) :: t() 106 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 107 - end 108 - 109 - defmodule Sh.Comet.V0.Richtext.Facet.ByteSlice do 110 - @moduledoc """ 111 - Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. 112 - """ 113 - 114 - @derive Jason.Encoder 115 - defstruct [ 116 - byteEnd: 0, 117 - byteStart: 0 118 - ] 119 - 120 - @type t() :: %__MODULE__{ 121 - byteEnd: integer, 122 - byteStart: integer 123 - } 124 - 125 - @spec new() :: t() 126 - def new(), do: %__MODULE__{} 127 - 128 - @spec from(binary() | map()) :: t() 129 - def from(json), do: Atproto.decode_to_struct(__MODULE__, json) 130 - end 131 -
-105
apps/backend/lib/atproto/tid.ex
··· 1 - defmodule Atproto.TID do 2 - @moduledoc """ 3 - A module for encoding and decoding TIDs. 4 - 5 - [TID](https://atproto.com/specs/tid) stands for "Timestamp Identifier". It is a 13-character string calculated from 53 bits representing a unix timestamp, in microsecond precision, plus 10 bits for an arbitrary "clock identifier", to help with uniqueness in distributed systems. 6 - 7 - The string is encoded as "base32-sortable", meaning that the characters for the base 32 encoding are set up in such a way that string comparisons yield the same result as integer comparisons, i.e. if the integer representation of the timestamp that creates TID "A" is greater than the integer representation of the timestamp that creates TID "B", then "A" > "B" is also true, and vice versa. 8 - """ 9 - 10 - import Bitwise 11 - 12 - @tid_char_set ~c(234567abcdefghijklmnopqrstuvwxyz) 13 - @tid_char_set_length 32 14 - 15 - defstruct [ 16 - :timestamp, 17 - :clock_id, 18 - :string 19 - ] 20 - 21 - @typedoc """ 22 - TIDs are composed of two parts: a timestamp and a clock identifier. They also have a human-readable string representation as a "base32-sortable" encoded string. 23 - """ 24 - @type t() :: %__MODULE__{ 25 - timestamp: integer(), 26 - clock_id: integer(), 27 - string: binary() 28 - } 29 - 30 - @doc """ 31 - Generates a random 10-bit clock identifier. 32 - """ 33 - @spec random_clock_id() :: integer() 34 - def random_clock_id(), do: :rand.uniform(1024) - 1 35 - 36 - @doc """ 37 - Generates a new TID for the current time. 38 - 39 - This is equivalent to calling `encode(nil)`. 40 - """ 41 - @spec new() :: t() 42 - def new(), do: encode(nil) 43 - 44 - @doc """ 45 - Encodes an integer or DateTime struct into a 13-character string that is "base32-sortable" encoded. 46 - 47 - If `timestamp` is nil, or not provided, the current time will be used as represented by `DateTime.utc_now()`. 48 - 49 - If `clock_id` is nil, or not provided, a random 10-bit integer will be used. 50 - 51 - If `timestamp` is an integer value, it *MUST* be a unix timestamp measured in microseconds. This function does not validate integer values. 52 - """ 53 - @spec encode(nil | integer() | DateTime.t(), nil | integer()) :: t() 54 - def encode(timestamp \\ nil, clock_id \\ nil) 55 - 56 - def encode(nil, clock_id), do: encode(DateTime.utc_now(), clock_id) 57 - 58 - def encode(timestamp, nil), do: encode(timestamp, random_clock_id()) 59 - 60 - def encode(%DateTime{} = datetime, clock_id) do 61 - datetime 62 - |> DateTime.to_unix(:microsecond) 63 - |> encode(clock_id) 64 - end 65 - 66 - def encode(timestamp, clock_id) when is_integer(timestamp) and is_integer(clock_id) do 67 - # Ensure we only use the lower 10 bit of clock_id 68 - clock_id = clock_id &&& 1023 69 - str = 70 - timestamp 71 - |> bsr(10) 72 - |> bsl(10) 73 - |> bxor(clock_id) 74 - |> do_encode("") 75 - %__MODULE__{timestamp: timestamp, clock_id: clock_id, string: str} 76 - end 77 - 78 - defp do_encode(0, acc), do: acc 79 - 80 - defp do_encode(number, acc) do 81 - c = rem(number, @tid_char_set_length) 82 - number = div(number, @tid_char_set_length) 83 - do_encode(number, <<Enum.at(@tid_char_set, c)>> <> acc) 84 - end 85 - 86 - @doc """ 87 - Decodes a binary string into a TID struct. 88 - """ 89 - @spec decode(binary()) :: t() 90 - def decode(tid) do 91 - num = do_decode(tid, 0) 92 - %__MODULE__{timestamp: bsr(num, 10), clock_id: num &&& 1023, string: tid} 93 - end 94 - 95 - defp do_decode(<<>>, acc), do: acc 96 - 97 - defp do_decode(<<char::utf8, rest::binary>>, acc) do 98 - idx = Enum.find_index(@tid_char_set, fn x -> x == char end) 99 - do_decode(rest, (acc * @tid_char_set_length) + idx) 100 - end 101 - end 102 - 103 - defimpl String.Chars, for: Atproto.TID do 104 - def to_string(tid), do: tid.string 105 - end
-34
apps/backend/lib/comet/application.ex
··· 1 - defmodule Comet.Application do 2 - # See https://hexdocs.pm/elixir/Application.html 3 - # for more information on OTP Applications 4 - @moduledoc false 5 - 6 - use Application 7 - 8 - @impl true 9 - def start(_type, _args) do 10 - children = [ 11 - CometWeb.Telemetry, 12 - Comet.Repo, 13 - {DNSCluster, query: Application.get_env(:comet, :dns_cluster_query) || :ignore}, 14 - {Phoenix.PubSub, name: Comet.PubSub}, 15 - # Start a worker by calling: Comet.Worker.start_link(arg) 16 - # {Comet.Worker, arg}, 17 - # Start to serve requests, typically the last entry 18 - CometWeb.Endpoint 19 - ] 20 - 21 - # See https://hexdocs.pm/elixir/Supervisor.html 22 - # for other strategies and supported options 23 - opts = [strategy: :one_for_one, name: Comet.Supervisor] 24 - Supervisor.start_link(children, opts) 25 - end 26 - 27 - # Tell Phoenix to update the endpoint configuration 28 - # whenever the application is updated. 29 - @impl true 30 - def config_change(changed, _new, removed) do 31 - CometWeb.Endpoint.config_change(changed, removed) 32 - :ok 33 - end 34 - end
-61
apps/backend/lib/comet/aturi.ex
··· 1 - defmodule Comet.AtURI do 2 - use TypedStruct 3 - 4 - @did "did:(?:plc|web):[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]" 5 - @handle "(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?" 6 - @nsid "[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)" 7 - 8 - @authority "(?<authority>(?:#{@did})|(?:#{@handle}))" 9 - @collection "(?<collection>#{@nsid})" 10 - @rkey "(?<rkey>[a-zA-Z0-9.-_:~]{1,512})" 11 - 12 - @re ~r"^at://#{@authority}(?:/#{@collection}(?:/#{@rkey})?)?$" 13 - 14 - typedstruct do 15 - field :authority, String.t(), enforce: true 16 - field :collection, String.t() | nil 17 - field :rkey, String.t() | nil 18 - end 19 - 20 - @spec new(String.t()) :: {:ok, t()} | :error 21 - def new(string) when is_binary(string) do 22 - case Regex.named_captures(@re, string) do 23 - %{} = captures -> {:ok, from_named_captures(captures)} 24 - nil -> :error 25 - end 26 - end 27 - 28 - @spec new!(String.t()) :: t() 29 - def new!(string) when is_binary(string) do 30 - case new(string) do 31 - {:ok, uri} -> uri 32 - :error -> raise ArgumentError, message: "Malformed at:// URI" 33 - end 34 - end 35 - 36 - @spec match?(String.t()) :: boolean() 37 - def match?(string), do: Regex.match?(@re, string) 38 - 39 - @spec to_string(t()) :: String.t() 40 - def to_string(%__MODULE__{} = uri) do 41 - "at://#{uri.authority}/#{uri.collection}/#{uri.rkey}" 42 - |> String.trim_trailing("/") 43 - end 44 - 45 - defp from_named_captures(%{"authority" => authority, "collection" => "", "rkey" => ""}), 46 - do: %__MODULE__{authority: authority} 47 - 48 - defp from_named_captures(%{"authority" => authority, "collection" => collection, "rkey" => ""}), 49 - do: %__MODULE__{authority: authority, collection: collection} 50 - 51 - defp from_named_captures(%{ 52 - "authority" => authority, 53 - "collection" => collection, 54 - "rkey" => rkey 55 - }), 56 - do: %__MODULE__{authority: authority, collection: collection, rkey: rkey} 57 - end 58 - 59 - defimpl String.Chars, for: Comet.AtURI do 60 - def to_string(%Comet.AtURI{} = uri), do: Comet.AtURI.to_string(uri) 61 - end
-31
apps/backend/lib/comet/repo/comment.ex
··· 1 - defmodule Comet.Repo.Comment do 2 - @moduledoc """ 3 - Schema containing information about a Comet comment. 4 - """ 5 - use Comet.Schema 6 - import Ecto.Changeset 7 - 8 - schema "comments" do 9 - field :rkey, :string 10 - field :text, :string 11 - embeds_one :facets, Repo.Embed.Facet, on_replace: :update 12 - field :subject_id, :binary_id 13 - field :subject_type, Ecto.Enum, values: [:track, :playlist] 14 - field :langs, {:array, :string} 15 - field :created_at, :utc_datetime 16 - 17 - belongs_to :identity, Repo.Identity, foreign_key: :did, references: :did 18 - belongs_to :parent, __MODULE__, foreign_key: :reply_id 19 - has_many :replies, __MODULE__, foreign_key: :reply_id 20 - 21 - timestamps(inserted_at: :indexed_at, updated_at: false) 22 - end 23 - 24 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 25 - 26 - def changeset(struct, params \\ %{}) do 27 - struct 28 - |> cast(params, [:rkey, :did, :text, :facets, :subject_id, :subject_type, :langs, :created_at]) 29 - |> validate_required([:rkey, :text]) 30 - end 31 - end
-22
apps/backend/lib/comet/repo/embed/facet.ex
··· 1 - defmodule Comet.Repo.Embed.Facet do 2 - use Comet.Schema 3 - import Ecto.Changeset 4 - 5 - @primary_key false 6 - embedded_schema do 7 - embeds_one :index, ByteSlice do 8 - field :byte_start, :integer 9 - field :byte_end, :integer 10 - end 11 - 12 - # Sadly Ecto doesn't support union types/embeds so this has to be generic, without doing weirdness in the database at least 13 - field :features, {:array, :map} 14 - end 15 - 16 - def changeset(struct, params \\ %{}) do 17 - struct 18 - |> cast(params, [:features]) 19 - |> cast_embed(:index, required: true) 20 - |> validate_required([:features]) 21 - end 22 - end
-16
apps/backend/lib/comet/repo/embed/link.ex
··· 1 - defmodule Comet.Repo.Embed.Link do 2 - use Comet.Schema 3 - import Ecto.Changeset 4 - 5 - @primary_key false 6 - embedded_schema do 7 - field :type, :string 8 - field :value, :string 9 - end 10 - 11 - def changeset(struct, params \\ %{}) do 12 - struct 13 - |> cast(params, [:type, :value]) 14 - |> validate_required([:type, :value]) 15 - end 16 - end
-26
apps/backend/lib/comet/repo/identity.ex
··· 1 - defmodule Comet.Repo.Identity do 2 - @moduledoc """ 3 - Schema containing information about an ATProtocol identity. 4 - """ 5 - use Ecto.Schema 6 - import Ecto.Changeset 7 - 8 - @primary_key {:did, :string, autogenerate: false} 9 - @foreign_key_type :string 10 - 11 - schema "identity" do 12 - field :handle, :string 13 - field :active, :boolean 14 - field :status, :string 15 - 16 - timestamps(inserted_at: :indexed_at, updated_at: false) 17 - end 18 - 19 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 20 - 21 - def changeset(struct, params \\ %{}) do 22 - struct 23 - |> cast(params, [:did, :handle, :active, :status]) 24 - |> validate_required([:did, :active]) 25 - end 26 - end
-26
apps/backend/lib/comet/repo/like.ex
··· 1 - defmodule Comet.Repo.Like do 2 - @moduledoc """ 3 - Schema containing information about a Comet like. 4 - """ 5 - use Comet.Schema 6 - import Ecto.Changeset 7 - 8 - schema "likes" do 9 - field :rkey, :string 10 - field :subject_id, :binary_id 11 - field :subject_type, Ecto.Enum, values: [:track, :playlist] 12 - field :created_at, :utc_datetime 13 - 14 - belongs_to :identity, Repo.Identity, foreign_key: :did, references: :did 15 - 16 - timestamps(inserted_at: :indexed_at, updated_at: false) 17 - end 18 - 19 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 20 - 21 - def changeset(struct, params \\ %{}) do 22 - struct 23 - |> cast(params, [:rkey, :did, :subject_id, :subject_type, :created_at]) 24 - |> validate_required([:rkey, :did, :subject_id, :subject_type, :created_at]) 25 - end 26 - end
-35
apps/backend/lib/comet/repo/playlist.ex
··· 1 - defmodule Comet.Repo.Playlist do 2 - @moduledoc """ 3 - Sch ema containing information about a Comet playlist. 4 - """ 5 - use Comet.Schema 6 - import Ecto.Changeset 7 - 8 - schema "playlists" do 9 - field :rkey, :string 10 - field :title, :string 11 - field :image, :string 12 - field :description, :string 13 - # TODO: see how this looks with/without primary id 14 - embeds_one :description_facets, Repo.Embed.Facet, on_replace: :update 15 - field :type, :string 16 - field :tags, {:array, :string} 17 - embeds_one :link, Repo.Embed.Link, on_replace: :update 18 - field :created_at, :utc_datetime 19 - 20 - belongs_to :identity, Repo.Identity, foreign_key: :did, references: :did 21 - has_many :tracks, Repo.Playlist 22 - 23 - timestamps(inserted_at: :indexed_at, updated_at: false) 24 - end 25 - 26 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 27 - 28 - def changeset(struct, params \\ %{}) do 29 - struct 30 - |> cast(params, [:rkey, :did, :title, :image, :description, :type, :tags, :created_at]) 31 - |> cast_embed(:description_facets) 32 - |> cast_embed(:link) 33 - |> validate_required([:rkey, :did, :title, :type, :created_at]) 34 - end 35 - end
-27
apps/backend/lib/comet/repo/playlist_track.ex
··· 1 - defmodule Comet.Repo.PlaylistTrack do 2 - @moduledoc """ 3 - Schema containing information about a track in a Comet playlist. 4 - """ 5 - use Comet.Schema 6 - import Ecto.Changeset 7 - 8 - schema "playlist_tracks" do 9 - field :rkey, :string 10 - field :position, :integer 11 - field :created_at, :utc_datetime 12 - 13 - belongs_to :identity, Repo.Identity, foreign_key: :did, references: :did 14 - belongs_to :track, Repo.Track 15 - belongs_to :playlist, Repo.Playlist 16 - 17 - timestamps(inserted_at: :indexed_at, updated_at: false) 18 - end 19 - 20 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 21 - 22 - def changeset(struct, params \\ %{}) do 23 - struct 24 - |> cast(params, [:rkey, :did, :position, :created_at, :track_id, :playlist_id]) 25 - |> validate_required([:rkey, :did, :position, :created_at, :track_id, :playlist_id]) 26 - end 27 - end
-41
apps/backend/lib/comet/repo/profile.ex
··· 1 - defmodule Comet.Repo.Profile do 2 - @moduledoc """ 3 - Schema containing information about a Comet profile. 4 - """ 5 - use Comet.Schema 6 - import Ecto.Changeset 7 - 8 - # TODO: should probably keep track of CID so as to not do unnecessary writes 9 - schema "profiles" do 10 - field :rkey, :string, default: "self" 11 - field :display_name, :string 12 - field :description, :string 13 - embeds_one :description_facets, Repo.Embed.Facet, on_replace: :update 14 - field :avatar, :string 15 - field :banner, :string 16 - field :featured_items, {:array, :string} 17 - field :created_at, :utc_datetime 18 - 19 - belongs_to :identity, Repo.Identity, foreign_key: :did, references: :did 20 - 21 - timestamps(inserted_at: :indexed_at, updated_at: false) 22 - end 23 - 24 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 25 - 26 - def changeset(struct, params \\ %{}) do 27 - struct 28 - |> cast(params, [ 29 - :rkey, 30 - :did, 31 - :display_name, 32 - :description, 33 - :avatar, 34 - :banner, 35 - :featured_items, 36 - :created_at 37 - ]) 38 - |> cast_embed(:description_facets) 39 - |> validate_required([:rkey, :did]) 40 - end 41 - end
-26
apps/backend/lib/comet/repo/repost.ex
··· 1 - defmodule Comet.Repo.Repost do 2 - @moduledoc """ 3 - Schema containing information about a Comet repost. 4 - """ 5 - use Comet.Schema 6 - import Ecto.Changeset 7 - 8 - schema "reposts" do 9 - field :rkey, :string 10 - field :subject_id, :binary_id 11 - field :subject_type, Ecto.Enum, values: [:track, :playlist] 12 - field :created_at, :utc_datetime 13 - 14 - belongs_to :identity, Repo.Identity, foreign_key: :did, references: :did 15 - 16 - timestamps(inserted_at: :indexed_at, updated_at: false) 17 - end 18 - 19 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 20 - 21 - def changeset(struct, params \\ %{}) do 22 - struct 23 - |> cast(params, [:rkey, :did, :subject_id, :subject_type, :created_at]) 24 - |> validate_required([:rkey, :did, :subject_id, :subject_type, :created_at]) 25 - end 26 - end
-46
apps/backend/lib/comet/repo/track.ex
··· 1 - defmodule Comet.Repo.Track do 2 - @moduledoc """ 3 - Schema containing information about a Comet track. 4 - """ 5 - use Comet.Schema 6 - import Ecto.Changeset 7 - 8 - schema "tracks" do 9 - field :rkey, :string 10 - field :title, :string 11 - field :audio, :string 12 - field :image, :string 13 - field :description, :string 14 - embeds_one :description_facets, Repo.Embed.Facet, on_replace: :update 15 - field :explicit, :boolean 16 - field :tags, {:array, :string} 17 - embeds_one :link, Repo.Embed.Link, on_replace: :update 18 - field :created_at, :utc_datetime 19 - field :released_at, :utc_datetime 20 - 21 - belongs_to :identity, Repo.Identity, foreign_key: :did, references: :did 22 - 23 - timestamps(inserted_at: :indexed_at, updated_at: false) 24 - end 25 - 26 - def new(params \\ %{}), do: changeset(%__MODULE__{}, params) 27 - 28 - def changeset(struct, params \\ %{}) do 29 - struct 30 - |> cast(params, [ 31 - :rkey, 32 - :did, 33 - :title, 34 - :audio, 35 - :image, 36 - :description, 37 - :explicit, 38 - :tags, 39 - :created_at, 40 - :released_at 41 - ]) 42 - |> cast_embed(:description_facets) 43 - |> cast_embed(:link) 44 - |> validate_required([:rkey, :did, :audio, :title, :created_at]) 45 - end 46 - end
-5
apps/backend/lib/comet/repo.ex
··· 1 - defmodule Comet.Repo do 2 - use Ecto.Repo, 3 - otp_app: :comet, 4 - adapter: Ecto.Adapters.Postgres 5 - end
-11
apps/backend/lib/comet/schema.ex
··· 1 - defmodule Comet.Schema do 2 - defmacro __using__(_) do 3 - quote do 4 - use Ecto.Schema 5 - alias Comet.Repo 6 - 7 - @primary_key {:id, :binary_id, autogenerate: true} 8 - @foreign_key_type :binary_id 9 - end 10 - end 11 - end
-9
apps/backend/lib/comet.ex
··· 1 - defmodule Comet do 2 - @moduledoc """ 3 - Comet keeps the contexts that define your domain 4 - and business logic. 5 - 6 - Contexts are also responsible for managing your data, regardless 7 - if it comes from the database, an external API or others. 8 - """ 9 - end
-21
apps/backend/lib/comet_web/controllers/error_json.ex
··· 1 - defmodule CometWeb.ErrorJSON do 2 - @moduledoc """ 3 - This module is invoked by your endpoint in case of errors on JSON requests. 4 - 5 - See config/config.exs. 6 - """ 7 - 8 - # If you want to customize a particular status code, 9 - # you may add your own clauses, such as: 10 - # 11 - # def render("500.json", _assigns) do 12 - # %{errors: %{detail: "Internal Server Error"}} 13 - # end 14 - 15 - # By default, Phoenix returns the status message from 16 - # the template name. For example, "404.json" becomes 17 - # "Not Found". 18 - def render(template, _assigns) do 19 - %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 20 - end 21 - end
-51
apps/backend/lib/comet_web/endpoint.ex
··· 1 - defmodule CometWeb.Endpoint do 2 - use Phoenix.Endpoint, otp_app: :comet 3 - 4 - # The session will be stored in the cookie and signed, 5 - # this means its contents can be read but not tampered with. 6 - # Set :encryption_salt if you would also like to encrypt it. 7 - @session_options [ 8 - store: :cookie, 9 - key: "_comet_key", 10 - signing_salt: "zgKytneJ", 11 - same_site: "Lax" 12 - ] 13 - 14 - socket "/live", Phoenix.LiveView.Socket, 15 - websocket: [connect_info: [session: @session_options]], 16 - longpoll: [connect_info: [session: @session_options]] 17 - 18 - # Serve at "/" the static files from "priv/static" directory. 19 - # 20 - # You should set gzip to true if you are running phx.digest 21 - # when deploying your static files in production. 22 - plug Plug.Static, 23 - at: "/", 24 - from: :comet, 25 - gzip: false, 26 - only: CometWeb.static_paths() 27 - 28 - # Code reloading can be explicitly enabled under the 29 - # :code_reloader configuration of your endpoint. 30 - if code_reloading? do 31 - plug Phoenix.CodeReloader 32 - plug Phoenix.Ecto.CheckRepoStatus, otp_app: :comet 33 - end 34 - 35 - plug Phoenix.LiveDashboard.RequestLogger, 36 - param_key: "request_logger", 37 - cookie_key: "request_logger" 38 - 39 - plug Plug.RequestId 40 - plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 41 - 42 - plug Plug.Parsers, 43 - parsers: [:urlencoded, :multipart, :json], 44 - pass: ["*/*"], 45 - json_decoder: Phoenix.json_library() 46 - 47 - plug Plug.MethodOverride 48 - plug Plug.Head 49 - plug Plug.Session, @session_options 50 - plug CometWeb.Router 51 - end
-27
apps/backend/lib/comet_web/router.ex
··· 1 - defmodule CometWeb.Router do 2 - use CometWeb, :router 3 - 4 - pipeline :api do 5 - plug :accepts, ["json"] 6 - end 7 - 8 - scope "/api", CometWeb do 9 - pipe_through :api 10 - end 11 - 12 - # Enable LiveDashboard in development 13 - if Application.compile_env(:comet, :dev_routes) do 14 - # If you want to use the LiveDashboard in production, you should put 15 - # it behind authentication and allow only admins to access it. 16 - # If your application does not have an admins-only section yet, 17 - # you can use Plug.BasicAuth to set up some basic authentication 18 - # as long as you are also using SSL (which you should anyway). 19 - import Phoenix.LiveDashboard.Router 20 - 21 - scope "/dev" do 22 - pipe_through [:fetch_session, :protect_from_forgery] 23 - 24 - live_dashboard "/dashboard", metrics: CometWeb.Telemetry 25 - end 26 - end 27 - end
-93
apps/backend/lib/comet_web/telemetry.ex
··· 1 - defmodule CometWeb.Telemetry do 2 - use Supervisor 3 - import Telemetry.Metrics 4 - 5 - def start_link(arg) do 6 - Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 - end 8 - 9 - @impl true 10 - def init(_arg) do 11 - children = [ 12 - # Telemetry poller will execute the given period measurements 13 - # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 - {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 - # Add reporters as children of your supervision tree. 16 - # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 - ] 18 - 19 - Supervisor.init(children, strategy: :one_for_one) 20 - end 21 - 22 - def metrics do 23 - [ 24 - # Phoenix Metrics 25 - summary("phoenix.endpoint.start.system_time", 26 - unit: {:native, :millisecond} 27 - ), 28 - summary("phoenix.endpoint.stop.duration", 29 - unit: {:native, :millisecond} 30 - ), 31 - summary("phoenix.router_dispatch.start.system_time", 32 - tags: [:route], 33 - unit: {:native, :millisecond} 34 - ), 35 - summary("phoenix.router_dispatch.exception.duration", 36 - tags: [:route], 37 - unit: {:native, :millisecond} 38 - ), 39 - summary("phoenix.router_dispatch.stop.duration", 40 - tags: [:route], 41 - unit: {:native, :millisecond} 42 - ), 43 - summary("phoenix.socket_connected.duration", 44 - unit: {:native, :millisecond} 45 - ), 46 - sum("phoenix.socket_drain.count"), 47 - summary("phoenix.channel_joined.duration", 48 - unit: {:native, :millisecond} 49 - ), 50 - summary("phoenix.channel_handled_in.duration", 51 - tags: [:event], 52 - unit: {:native, :millisecond} 53 - ), 54 - 55 - # Database Metrics 56 - summary("comet.repo.query.total_time", 57 - unit: {:native, :millisecond}, 58 - description: "The sum of the other measurements" 59 - ), 60 - summary("comet.repo.query.decode_time", 61 - unit: {:native, :millisecond}, 62 - description: "The time spent decoding the data received from the database" 63 - ), 64 - summary("comet.repo.query.query_time", 65 - unit: {:native, :millisecond}, 66 - description: "The time spent executing the query" 67 - ), 68 - summary("comet.repo.query.queue_time", 69 - unit: {:native, :millisecond}, 70 - description: "The time spent waiting for a database connection" 71 - ), 72 - summary("comet.repo.query.idle_time", 73 - unit: {:native, :millisecond}, 74 - description: 75 - "The time the connection spent waiting before being checked out for the query" 76 - ), 77 - 78 - # VM Metrics 79 - summary("vm.memory.total", unit: {:byte, :kilobyte}), 80 - summary("vm.total_run_queue_lengths.total"), 81 - summary("vm.total_run_queue_lengths.cpu"), 82 - summary("vm.total_run_queue_lengths.io") 83 - ] 84 - end 85 - 86 - defp periodic_measurements do 87 - [ 88 - # A module, function and arguments to be invoked periodically. 89 - # This function must call :telemetry.execute/3 and a metric must be added above. 90 - # {CometWeb, :count_users, []} 91 - ] 92 - end 93 - end
-65
apps/backend/lib/comet_web.ex
··· 1 - defmodule CometWeb do 2 - @moduledoc """ 3 - The entrypoint for defining your web interface, such 4 - as controllers, components, channels, and so on. 5 - 6 - This can be used in your application as: 7 - 8 - use CometWeb, :controller 9 - use CometWeb, :html 10 - 11 - The definitions below will be executed for every controller, 12 - component, etc, so keep them short and clean, focused 13 - on imports, uses and aliases. 14 - 15 - Do NOT define functions inside the quoted expressions 16 - below. Instead, define additional modules and import 17 - those modules here. 18 - """ 19 - 20 - def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) 21 - 22 - def router do 23 - quote do 24 - use Phoenix.Router, helpers: false 25 - 26 - # Import common connection and controller functions to use in pipelines 27 - import Plug.Conn 28 - import Phoenix.Controller 29 - end 30 - end 31 - 32 - def channel do 33 - quote do 34 - use Phoenix.Channel 35 - end 36 - end 37 - 38 - def controller do 39 - quote do 40 - use Phoenix.Controller, 41 - formats: [:html, :json], 42 - layouts: [html: CometWeb.Layouts] 43 - 44 - import Plug.Conn 45 - 46 - unquote(verified_routes()) 47 - end 48 - end 49 - 50 - def verified_routes do 51 - quote do 52 - use Phoenix.VerifiedRoutes, 53 - endpoint: CometWeb.Endpoint, 54 - router: CometWeb.Router, 55 - statics: CometWeb.static_paths() 56 - end 57 - end 58 - 59 - @doc """ 60 - When used, dispatch to the appropriate controller/live_view/etc. 61 - """ 62 - defmacro __using__(which) when is_atom(which) do 63 - apply(__MODULE__, which, []) 64 - end 65 - end
-70
apps/backend/mix.exs
··· 1 - defmodule Comet.MixProject do 2 - use Mix.Project 3 - 4 - def project do 5 - [ 6 - app: :comet, 7 - version: "0.1.0", 8 - elixir: "~> 1.14", 9 - elixirc_paths: elixirc_paths(Mix.env()), 10 - start_permanent: Mix.env() == :prod, 11 - aliases: aliases(), 12 - deps: deps() 13 - ] 14 - end 15 - 16 - # Configuration for the OTP application. 17 - # 18 - # Type `mix help compile.app` for more information. 19 - def application do 20 - [ 21 - mod: {Comet.Application, []}, 22 - extra_applications: [:logger, :runtime_tools] 23 - ] 24 - end 25 - 26 - # Specifies which paths to compile per environment. 27 - defp elixirc_paths(:test), do: ["lib", "test/support"] 28 - defp elixirc_paths(_), do: ["lib"] 29 - 30 - # Specifies your project dependencies. 31 - # 32 - # Type `mix help deps` for examples and options. 33 - defp deps do 34 - [ 35 - {:phoenix, "~> 1.7.21"}, 36 - {:phoenix_ecto, "~> 4.5"}, 37 - {:ecto_sql, "~> 3.10"}, 38 - {:postgrex, ">= 0.0.0"}, 39 - {:phoenix_live_dashboard, "~> 0.8.3"}, 40 - {:telemetry_metrics, "~> 1.0"}, 41 - {:telemetry_poller, "~> 1.0"}, 42 - {:jason, "~> 1.2"}, 43 - {:dns_cluster, "~> 0.1.1"}, 44 - {:bandit, "~> 1.5"}, 45 - {:lexgen, "~> 1.0.0", only: [:dev]}, 46 - {:req, "~> 0.5.0"}, 47 - {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, 48 - {:drinkup, "~> 0.1"}, 49 - {:typedstruct, "~> 0.5"} 50 - ] 51 - end 52 - 53 - # Aliases are shortcuts or tasks specific to the current project. 54 - # For example, to install project dependencies and perform other setup tasks, run: 55 - # 56 - # $ mix setup 57 - # 58 - # See the documentation for `Mix` for more info on aliases. 59 - defp aliases do 60 - lexicon_paths = Path.wildcard("../../packages/lexicons/defs/**/*.json") 61 - 62 - [ 63 - setup: ["deps.get", "ecto.setup"], 64 - "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 65 - "ecto.reset": ["ecto.drop", "ecto.setup"], 66 - test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], 67 - "gen.lexicons": ["lexgen" | lexicon_paths] |> Enum.join(" ") 68 - ] 69 - end 70 - end
-45
apps/backend/mix.lock
··· 1 - %{ 2 - "bandit": {:hex, :bandit, "1.7.0", "d1564f30553c97d3e25f9623144bb8df11f3787a26733f00b21699a128105c0c", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3e2f7a98c7a11f48d9d8c037f7177cd39778e74d55c7af06fe6227c742a8168a"}, 3 - "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 4 - "car": {:hex, :car, "0.1.1", "a5bc4c5c1be96eab437634b3c0ccad1fe17b5e3d68c22a4031241ae1345aebd4", [:mix], [{:cbor, "~> 1.0.0", [hex: :cbor, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}, {:varint, "~> 1.4", [hex: :varint, repo: "hexpm", optional: false]}], "hexpm", "f895dda8123d04dd336db5a2bf0d0b47f4559cd5383f83fcca0700c1b45bfb6a"}, 5 - "castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"}, 6 - "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, 7 - "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, 8 - "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, 9 - "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [: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", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, 10 - "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, 11 - "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, 12 - "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, 13 - "drinkup": {:hex, :drinkup, "0.1.0", "a21d563163a0a19db448820f0c4cf9278d52e94b7276b099c60cb8544b16b14e", [:mix], [{:car, "~> 0.1.0", [hex: :car, repo: "hexpm", optional: false]}, {:cbor, "~> 1.0.0", [hex: :cbor, repo: "hexpm", optional: false]}, {:certifi, "~> 2.15", [hex: :certifi, repo: "hexpm", optional: false]}, {:gun, "~> 2.2", [hex: :gun, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}], "hexpm", "e2a22b12386936f4a62c6307050c3484c41ecd07c50dc1b1af653195d539baa2"}, 14 - "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 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", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, 15 - "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, 16 - "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, 17 - "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, 18 - "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, 19 - "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, 20 - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 21 - "lexgen": {:hex, :lexgen, "1.0.0", "1ca22ba00b86f9fa97718651b77b87a5965b8a9f71109ac2c11cb573f17499aa", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "ff64e0e192645208e7ce1b6468037a6d4ebfb98a506ab15d30fb46ca492ec275"}, 22 - "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, 23 - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, 24 - "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, 25 - "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, 26 - "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [: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", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, 27 - "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.4", "dcf3483ab45bab4c15e3a47c34451392f64e433846b08469f5d16c2a4cd70052", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f5b8584c36ccc9b903948a696fc9b8b81102c79c7c0c751a9f00cdec55d5f2d7"}, 28 - "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, 29 - "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, 30 - "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.14", "621f075577e286ff1e67d6de085ddf6f364f934d229c1c5564be1ef4c77908b9", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6dcb3f236044cd9d1c0d0996331bef72716b1991bbd8e0725a617c0d95a9483"}, 31 - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, 32 - "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"}, 33 - "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [: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", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"}, 34 - "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, 35 - "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [: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", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, 36 - "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, 37 - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, 38 - "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, 39 - "telemetry_poller": {:hex, :telemetry_poller, "1.2.0", "ba82e333215aed9dd2096f93bd1d13ae89d249f82760fcada0850ba33bac154b", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7216e21a6c326eb9aa44328028c34e9fd348fb53667ca837be59d0aa2a0156e8"}, 40 - "thousand_island": {:hex, :thousand_island, "1.3.14", "ad45ebed2577b5437582bcc79c5eccd1e2a8c326abf6a3464ab6c06e2055a34a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d24a929d31cdd1d7903a4fe7f2409afeedff092d277be604966cd6aa4307ef"}, 41 - "typedstruct": {:hex, :typedstruct, "0.5.3", "d68ae424251a41b81a8d0c485328ab48edbd3858f3565bbdac21b43c056fc9b4", [:make, :mix], [], "hexpm", "b53b8186701417c0b2782bf02a2db5524f879b8488f91d1d83b97d84c2943432"}, 42 - "varint": {:hex, :varint, "1.5.1", "17160c70d0428c3f8a7585e182468cac10bbf165c2360cf2328aaa39d3fb1795", [:mix], [], "hexpm", "24f3deb61e91cb988056de79d06f01161dd01be5e0acae61d8d936a552f1be73"}, 43 - "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, 44 - "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [: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", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, 45 - }
-4
apps/backend/priv/repo/migrations/.formatter.exs
··· 1 - [ 2 - import_deps: [:ecto_sql], 3 - inputs: ["*.exs"] 4 - ]
-129
apps/backend/priv/repo/migrations/20250602100037_init.exs
··· 1 - defmodule Comet.Repo.Migrations.Init do 2 - use Ecto.Migration 3 - 4 - defmacrop did_rkey do 5 - quote do 6 - add :did, references(:identity, column: :did, type: :string), null: false 7 - add :rkey, :string, null: false 8 - end 9 - end 10 - 11 - def change do 12 - create table(:identity, primary_key: false) do 13 - add :did, :string, primary_key: true, null: false 14 - add :handle, :string 15 - add :active, :boolean, null: false 16 - add :status, :string 17 - # TODO: cache of did record? 18 - timestamps(inserted_at: :indexed_at, updated_at: false) 19 - end 20 - 21 - create table(:profiles) do 22 - did_rkey() 23 - add :display_name, :string 24 - add :description, :string 25 - # TODO: as a table? 26 - add :description_facets, :map 27 - add :avatar, :string 28 - add :banner, :string 29 - # TODO: as a table? 30 - add :featured_items, {:array, :string} 31 - add :created_at, :utc_datetime 32 - timestamps(inserted_at: :indexed_at, updated_at: false) 33 - end 34 - 35 - create unique_index(:profiles, [:did, :rkey]) 36 - 37 - create table(:tracks) do 38 - did_rkey() 39 - add :title, :string, null: false 40 - add :audio, :string, null: false 41 - add :image, :string 42 - add :description, :string 43 - add :description_facets, :map 44 - add :explicit, :boolean 45 - # TODO: table for easier linking? 46 - add :tags, {:array, :string} 47 - add :link, :map 48 - add :created_at, :utc_datetime, null: false 49 - add :released_at, :utc_datetime 50 - timestamps(inserted_at: :indexed_at, updated_at: false) 51 - end 52 - 53 - create index(:tracks, [:did, :rkey]) 54 - 55 - create table(:playlists) do 56 - did_rkey() 57 - add :title, :string, null: false 58 - add :image, :string 59 - add :description, :string 60 - add :description_facets, :map 61 - add :type, :string, null: false 62 - add :tags, {:array, :string} 63 - add :link, :map 64 - add :created_at, :utc_datetime, null: false 65 - timestamps(inserted_at: :indexed_at, updated_at: false) 66 - end 67 - 68 - # TODO: probably still can do unique index on this 69 - create index(:playlists, [:did, :rkey]) 70 - 71 - create table(:playlist_tracks) do 72 - did_rkey() 73 - 74 - add :track_id, references(:tracks), null: false 75 - add :playlist_id, references(:playlists), null: false 76 - 77 - add :position, :integer, null: true 78 - add :created_at, :utc_datetime, null: false 79 - timestamps(inserted_at: :indexed_at, updated_at: false) 80 - end 81 - 82 - create index(:playlist_tracks, [:did, :rkey]) 83 - # create unique_index() 84 - 85 - create table(:likes) do 86 - did_rkey() 87 - # add :subject_did, :string, null: false 88 - add :subject_id, :binary_id, null: false 89 - add :subject_type, :string, null: false 90 - add :created_at, :utc_datetime, null: false 91 - end 92 - 93 - create index(:likes, [:did, :rkey]) 94 - create unique_index(:likes, [:did, :subject_id]) 95 - 96 - create table(:reposts) do 97 - did_rkey() 98 - # add :subject_did, :string, null: false 99 - add :subject_id, :binary_id, null: false 100 - add :subject_type, :string, null: false 101 - add :created_at, :utc_datetime, null: false 102 - end 103 - 104 - create index(:reposts, [:did, :rkey]) 105 - create unique_index(:reposts, [:did, :subject_id]) 106 - 107 - create table(:plays) do 108 - did_rkey() 109 - add :subject, references(:tracks), null: false 110 - add :created_at, :utc_datetime, null: false 111 - end 112 - 113 - create index(:plays, [:did, :rkey]) 114 - 115 - create table(:comments) do 116 - did_rkey() 117 - add :text, :string, null: false 118 - add :subject_id, :binary_id, null: false 119 - add :subject_type, :string, null: false 120 - add :reply_id, references(:comments) 121 - # add :reply, :string 122 - add :langs, {:array, :string} 123 - add :facets, :map 124 - add :created_at, :utc_datetime, null: false 125 - end 126 - 127 - create index(:comments, [:did, :rkey]) 128 - end 129 - end
-11
apps/backend/priv/repo/seeds.exs
··· 1 - # Script for populating the database. You can run it as: 2 - # 3 - # mix run priv/repo/seeds.exs 4 - # 5 - # Inside the script, you can read and write to any of your 6 - # repositories directly: 7 - # 8 - # Comet.Repo.insert!(%Comet.SomeSchema{}) 9 - # 10 - # We recommend using the bang functions (`insert!`, `update!` 11 - # and so on) as they will fail if something goes wrong.
apps/backend/priv/static/favicon.ico

This is a binary file and will not be displayed.

-5
apps/backend/priv/static/robots.txt
··· 1 - # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 - # 3 - # To ban all spiders from the entire site uncomment the next two lines: 4 - # User-agent: * 5 - # Disallow: /
-12
apps/backend/test/comet_web/controllers/error_json_test.exs
··· 1 - defmodule CometWeb.ErrorJSONTest do 2 - use CometWeb.ConnCase, async: true 3 - 4 - test "renders 404" do 5 - assert CometWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} 6 - end 7 - 8 - test "renders 500" do 9 - assert CometWeb.ErrorJSON.render("500.json", %{}) == 10 - %{errors: %{detail: "Internal Server Error"}} 11 - end 12 - end
-38
apps/backend/test/support/conn_case.ex
··· 1 - defmodule CometWeb.ConnCase do 2 - @moduledoc """ 3 - This module defines the test case to be used by 4 - tests that require setting up a connection. 5 - 6 - Such tests rely on `Phoenix.ConnTest` and also 7 - import other functionality to make it easier 8 - to build common data structures and query the data layer. 9 - 10 - Finally, if the test case interacts with the database, 11 - we enable the SQL sandbox, so changes done to the database 12 - are reverted at the end of every test. If you are using 13 - PostgreSQL, you can even run database tests asynchronously 14 - by setting `use CometWeb.ConnCase, async: true`, although 15 - this option is not recommended for other databases. 16 - """ 17 - 18 - use ExUnit.CaseTemplate 19 - 20 - using do 21 - quote do 22 - # The default endpoint for testing 23 - @endpoint CometWeb.Endpoint 24 - 25 - use CometWeb, :verified_routes 26 - 27 - # Import conveniences for testing with connections 28 - import Plug.Conn 29 - import Phoenix.ConnTest 30 - import CometWeb.ConnCase 31 - end 32 - end 33 - 34 - setup tags do 35 - Comet.DataCase.setup_sandbox(tags) 36 - {:ok, conn: Phoenix.ConnTest.build_conn()} 37 - end 38 - end
-58
apps/backend/test/support/data_case.ex
··· 1 - defmodule Comet.DataCase do 2 - @moduledoc """ 3 - This module defines the setup for tests requiring 4 - access to the application's data layer. 5 - 6 - You may define functions here to be used as helpers in 7 - your tests. 8 - 9 - Finally, if the test case interacts with the database, 10 - we enable the SQL sandbox, so changes done to the database 11 - are reverted at the end of every test. If you are using 12 - PostgreSQL, you can even run database tests asynchronously 13 - by setting `use Comet.DataCase, async: true`, although 14 - this option is not recommended for other databases. 15 - """ 16 - 17 - use ExUnit.CaseTemplate 18 - 19 - using do 20 - quote do 21 - alias Comet.Repo 22 - 23 - import Ecto 24 - import Ecto.Changeset 25 - import Ecto.Query 26 - import Comet.DataCase 27 - end 28 - end 29 - 30 - setup tags do 31 - Comet.DataCase.setup_sandbox(tags) 32 - :ok 33 - end 34 - 35 - @doc """ 36 - Sets up the sandbox based on the test tags. 37 - """ 38 - def setup_sandbox(tags) do 39 - pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Comet.Repo, shared: not tags[:async]) 40 - on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) 41 - end 42 - 43 - @doc """ 44 - A helper that transforms changeset errors into a map of messages. 45 - 46 - assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 47 - assert "password is too short" in errors_on(changeset).password 48 - assert %{password: ["password is too short"]} = errors_on(changeset) 49 - 50 - """ 51 - def errors_on(changeset) do 52 - Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 53 - Regex.replace(~r"%{(\w+)}", message, fn _, key -> 54 - opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() 55 - end) 56 - end) 57 - end 58 - end
-2
apps/backend/test/test_helper.exs
··· 1 - ExUnit.start() 2 - Ecto.Adapters.SQL.Sandbox.mode(Comet.Repo, :manual)
-37
apps/frontend/eslint.config.js
··· 1 - import prettier from "eslint-config-prettier"; 2 - import js from "@eslint/js"; 3 - import { includeIgnoreFile } from "@eslint/compat"; 4 - import svelte from "eslint-plugin-svelte"; 5 - import globals from "globals"; 6 - import { fileURLToPath } from "node:url"; 7 - import ts from "typescript-eslint"; 8 - import svelteConfig from "./svelte.config.js"; 9 - 10 - const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url)); 11 - 12 - export default ts.config( 13 - includeIgnoreFile(gitignorePath), 14 - js.configs.recommended, 15 - ...ts.configs.recommended, 16 - ...svelte.configs.recommended, 17 - prettier, 18 - ...svelte.configs.prettier, 19 - { 20 - languageOptions: { 21 - globals: { ...globals.browser, ...globals.node }, 22 - }, 23 - rules: { "no-undef": "off" }, 24 - }, 25 - { 26 - files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], 27 - ignores: ["eslint.config.js", "svelte.config.js"], 28 - languageOptions: { 29 - parserOptions: { 30 - projectService: true, 31 - extraFileExtensions: [".svelte"], 32 - parser: ts.parser, 33 - svelteConfig, 34 - }, 35 - }, 36 - }, 37 - );
-36
apps/frontend/package.json
··· 1 - { 2 - "name": "comet", 3 - "private": true, 4 - "version": "0.0.1", 5 - "type": "module", 6 - "scripts": { 7 - "dev": "vite dev", 8 - "build": "vite build", 9 - "preview": "vite preview", 10 - "prepare": "svelte-kit sync || echo ''", 11 - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 13 - }, 14 - "devDependencies": { 15 - "@eslint/compat": "^1.2.5", 16 - "@eslint/js": "^9.18.0", 17 - "@fontsource-variable/inter": "^5.2.5", 18 - "@lucide/svelte": "^0.487.0", 19 - "@sveltejs/adapter-auto": "^4.0.0", 20 - "@sveltejs/kit": "^2.16.0", 21 - "@sveltejs/vite-plugin-svelte": "^5.0.0", 22 - "@tailwindcss/vite": "^4.0.0", 23 - "bits-ui": "^1.3.17", 24 - "clsx": "^2.1.1", 25 - "eslint": "^9.18.0", 26 - "eslint-config-prettier": "^10.0.1", 27 - "eslint-plugin-svelte": "^3.0.0", 28 - "globals": "^16.0.0", 29 - "svelte": "^5.0.0", 30 - "svelte-check": "^4.0.0", 31 - "tailwindcss": "^4.0.0", 32 - "typescript": "^5.0.0", 33 - "typescript-eslint": "^8.20.0", 34 - "vite": "^6.2.5" 35 - } 36 - }
-13
apps/frontend/src/app.css
··· 1 - @import "tailwindcss"; 2 - @import "@fontsource-variable/inter"; 3 - 4 - @theme { 5 - --font-sans: 6 - "Inter Variable", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", 7 - "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 8 - --font-sans--font-feature-settings: "tnum", "ss01", "ss02"; 9 - } 10 - 11 - body { 12 - @apply bg-slate-100; 13 - }
-13
apps/frontend/src/app.d.ts
··· 1 - // See https://svelte.dev/docs/kit/types#app.d.ts 2 - // for information about these interfaces 3 - declare global { 4 - namespace App { 5 - // interface Error {} 6 - // interface Locals {} 7 - // interface PageData {} 8 - // interface PageState {} 9 - // interface Platform {} 10 - } 11 - } 12 - 13 - export {};
-12
apps/frontend/src/app.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="utf-8" /> 5 - <link rel="icon" href="%sveltekit.assets%/favicon.png" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 - %sveltekit.head% 8 - </head> 9 - <body data-sveltekit-preload-data="hover"> 10 - <div style="display: contents">%sveltekit.body%</div> 11 - </body> 12 - </html>
-5
apps/frontend/src/lib/components/Navbar.svelte
··· 1 - <nav 2 - class="m-2 flex items-center rounded-lg border border-slate-300 bg-white p-4 font-bold text-slate-700" 3 - > 4 - Comet 5 - </nav>
-107
apps/frontend/src/lib/components/Player1.svelte
··· 1 - <script lang="ts"> 2 - import { 3 - List, 4 - Pause, 5 - Play, 6 - Repeat, 7 - Shuffle, 8 - SkipBack, 9 - SkipForward, 10 - Volume2, 11 - type Icon as LucideIcon, 12 - } from "@lucide/svelte"; 13 - import { Slider } from "bits-ui"; 14 - import cn from "clsx"; 15 - 16 - let playing = $state(true); 17 - let shuffle = $state(false); 18 - let repeat = $state(false); 19 - 20 - const MainIcon = $derived(playing ? Pause : Play); 21 - 22 - const songLength = 256; 23 - let playback = $state(0); 24 - </script> 25 - 26 - {#snippet plainButton(Icon: typeof LucideIcon, label: string)} 27 - <button class="flex cursor-pointer" aria-label={label}> 28 - <Icon /> 29 - </button> 30 - {/snippet} 31 - 32 - {#snippet clickable(content: string)} 33 - <span class="cursor-pointer hover:underline">{content}</span> 34 - {/snippet} 35 - 36 - <!-- TODO: labelled by the artist & title --> 37 - <aside 38 - class="fixed right-2 bottom-2 left-2 flex items-center gap-4 rounded-lg border border-slate-300 bg-white p-2 px-4 text-slate-500" 39 - > 40 - <div class="flex items-center gap-2 text-slate-900"> 41 - {@render plainButton(SkipBack, "Previous song")} 42 - <button 43 - class="flex cursor-pointer items-center justify-center rounded-full bg-orange-500 p-2 text-white" 44 - aria-label="Play" 45 - onclick={() => (playing = !playing)} 46 - > 47 - <MainIcon /> 48 - </button> 49 - {@render plainButton(SkipForward, "Next song")} 50 - </div> 51 - 52 - <div class="flex items-center gap-2"> 53 - <img 54 - src="https://lh3.googleusercontent.com/0z6Kg2GFi8hFgZYxWm3c3UNul0gyaCQjuqmY-p1oeFC1n5EMOf1dxrownTzhzk-_cdtO_FLLktQcMecwGQ=w544-h544-l90-rj" 55 - class="h-12 w-12 rounded object-cover object-center" 56 - alt="" 57 - /> 58 - <div class="flex flex-col"> 59 - <span class="text-sm font-semibold text-slate-900 opacity-70"> 60 - {@render clickable("Protostar")}, {@render clickable("Laminar")} & {@render clickable( 61 - "imallryt", 62 - )} 63 - </span> 64 - <span class="font-bolder text-sm font-semibold text-slate-900"> 65 - {@render clickable("Blood in the Water")} 66 - <!-- <span class="opacity-50">| {@render clickable("Epic Album")}</span> --> 67 - </span> 68 - </div> 69 - </div> 70 - 71 - <div class="flex flex-1 px-30"> 72 - <Slider.Root 73 - type="single" 74 - bind:value={playback} 75 - max={songLength} 76 - class="relative flex flex-1 touch-none items-center select-none" 77 - > 78 - {#snippet children()} 79 - <span 80 - class="relative h-1 w-full cursor-pointer overflow-hidden rounded-full bg-slate-200" 81 - > 82 - <Slider.Range class="absolute h-full rounded-full bg-orange-500" /> 83 - </span> 84 - <Slider.Thumb 85 - index={0} 86 - class="block size-4 cursor-pointer rounded-full border border-slate-900 bg-slate-50 focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-offset-2 " 87 - /> 88 - {/snippet} 89 - </Slider.Root> 90 - </div> 91 - 92 - <button 93 - class={cn("flex", "cursor-pointer", { "text-orange-500": shuffle })} 94 - onclick={() => (shuffle = !shuffle)} 95 - > 96 - <Shuffle /> 97 - </button> 98 - <button 99 - class={cn("flex", "cursor-pointer", { "text-orange-500": repeat })} 100 - onclick={() => (repeat = !repeat)} 101 - > 102 - <Repeat /> 103 - </button> 104 - 105 - <List class="cursor-pointer" /> 106 - <Volume2 class="cursor-pointer" /> 107 - </aside>
-201
apps/frontend/src/lib/components/Player2.svelte
··· 1 - <script lang="ts"> 2 - import { 3 - ChevronDown, 4 - List, 5 - Pause, 6 - Play, 7 - Repeat, 8 - Repeat1, 9 - Shuffle, 10 - SkipBack, 11 - SkipForward, 12 - Volume2, 13 - type Icon as LucideIcon, 14 - } from "@lucide/svelte"; 15 - import { Slider } from "bits-ui"; 16 - import cn from "clsx"; 17 - 18 - let expanded = $state(true); 19 - let playing = $state(false); 20 - let shuffle = $state(false); 21 - let repeat: "none" | "all" | "one" = $state("none"); 22 - let interval: ReturnType<typeof setInterval>; 23 - 24 - const MainIcon = $derived(playing ? Pause : Play); 25 - const RepeatIcon = $derived.by(() => (repeat === "one" ? Repeat1 : Repeat)); 26 - 27 - // TODO: separate progress state so that the thumb does not jump around to it's real value while the user is dragging it. 28 - // Probably done through checking click state of the thumb and temporarily disconnecting the progress? 29 - const songLength = 256; 30 - let playback = $state(0); 31 - 32 - $effect(() => { 33 - // console.log({ playback }); 34 - 35 - if (playing) 36 - interval = setInterval(() => { 37 - playback++; 38 - if (playback > songLength) playback = 0; 39 - }, 1000); 40 - else clearInterval(interval); 41 - }); 42 - 43 - // TODO: see if I need to i18n time format. 44 - const formatTime = (inputSeconds: number) => { 45 - const minutes = Math.floor(inputSeconds / 60); 46 - const seconds = `${inputSeconds % 60}`.padStart(2, "0"); 47 - 48 - return `${minutes}:${seconds}`; 49 - }; 50 - 51 - const cycleRepeat = () => { 52 - if (repeat === "none") repeat = "all"; 53 - else if (repeat === "all") repeat = "one"; 54 - else repeat = "none"; 55 - }; 56 - </script> 57 - 58 - {#snippet plainButton(Icon: typeof LucideIcon, label: string)} 59 - <button class="flex cursor-pointer" aria-label={label}> 60 - <Icon /> 61 - </button> 62 - {/snippet} 63 - 64 - {#snippet clickable(content: string)} 65 - <span class="cursor-pointer hover:underline">{content}</span> 66 - {/snippet} 67 - 68 - <!-- TODO: labelled by the artist & title --> 69 - <!-- TODO: keep width when collapsed? --> 70 - <aside 71 - class="fixed bottom-2 left-2 flex flex-col gap-2 overflow-hidden rounded-lg border border-slate-300 bg-white" 72 - > 73 - <header class="flex gap-2 bg-black p-2 text-slate-50"> 74 - <button 75 - class={cn("flex cursor-pointer transition-transform", { 76 - "rotate-180": !expanded, 77 - })} 78 - onclick={() => (expanded = !expanded)} 79 - > 80 - <ChevronDown /> 81 - </button> 82 - {#if expanded} 83 - <span class="font-bold">Now Playing</span> 84 - <div class="flex-1"></div> 85 - 86 - <button 87 - class={cn("flex", "cursor-pointer", { "text-orange-500": shuffle })} 88 - onclick={() => (shuffle = !shuffle)} 89 - > 90 - <Shuffle /> 91 - </button> 92 - <button 93 - class={cn("flex", "cursor-pointer", { 94 - "text-orange-500": repeat !== "none", 95 - })} 96 - onclick={cycleRepeat} 97 - > 98 - <RepeatIcon /> 99 - </button> 100 - 101 - <List class="cursor-pointer" /> 102 - <Volume2 class="cursor-pointer" /> 103 - {:else} 104 - <div class="flex gap-1"> 105 - <button 106 - class="flex cursor-pointer" 107 - aria-label={playing ? "Pause current song" : "Play current song"} 108 - onclick={() => (playing = !playing)} 109 - > 110 - <MainIcon /> 111 - </button> 112 - <!-- TODO: scrolling text --> 113 - <div class="line-clamp-1 max-w-[300px] text-ellipsis"> 114 - <span>Protostar, Laminar, imallryt</span> 115 - - 116 - <span>Blood in the Water</span> 117 - </div> 118 - 119 - <Volume2 class="cursor-pointer" /> 120 - </div> 121 - {/if} 122 - </header> 123 - 124 - {#if expanded} 125 - <div class="flex flex-col gap-2 px-4 py-2 text-slate-500"> 126 - <div class="flex items-center gap-4"> 127 - <div class="flex items-center gap-2 text-slate-900"> 128 - {@render plainButton(SkipBack, "Previous song")} 129 - <button 130 - class="flex cursor-pointer items-center justify-center rounded-full bg-orange-500 p-2 text-white" 131 - aria-label={playing ? "Pause current song" : "Play current song"} 132 - onclick={() => (playing = !playing)} 133 - > 134 - <MainIcon /> 135 - </button> 136 - {@render plainButton(SkipForward, "Next song")} 137 - </div> 138 - 139 - <div class="flex items-center gap-2"> 140 - <img 141 - src="https://lh3.googleusercontent.com/0z6Kg2GFi8hFgZYxWm3c3UNul0gyaCQjuqmY-p1oeFC1n5EMOf1dxrownTzhzk-_cdtO_FLLktQcMecwGQ=w544-h544-l90-rj" 142 - class="h-12 w-12 rounded object-cover object-center" 143 - alt="" 144 - /> 145 - <!-- TODO: max width with scrolling texts --> 146 - <div class="flex flex-col"> 147 - <span class="text-sm font-semibold text-slate-900 opacity-70"> 148 - {@render clickable("Protostar")}, {@render clickable("Laminar")} & 149 - {@render clickable("imallryt")} 150 - </span> 151 - <span class="font-bolder text-sm font-semibold text-slate-900"> 152 - {@render clickable("Blood in the Water")} 153 - <!-- <span class="opacity-50">| {@render clickable("Epic Album")}</span> --> 154 - </span> 155 - </div> 156 - </div> 157 - </div> 158 - 159 - <div class="flex w-full gap-2 py-2"> 160 - <Slider.Root 161 - type="single" 162 - max={songLength} 163 - class="relative flex flex-1 touch-none items-center select-none" 164 - value={playback} 165 - onValueCommit={(value) => (playback = value)} 166 - > 167 - {#snippet children()} 168 - <span 169 - class="relative h-1 w-full cursor-pointer overflow-hidden rounded-full bg-slate-200" 170 - > 171 - <Slider.Range 172 - class="absolute h-full rounded-full bg-orange-500" 173 - /> 174 - </span> 175 - <Slider.Thumb 176 - index={0} 177 - class="block size-4 cursor-pointer rounded-full border border-slate-900 bg-slate-50 focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-offset-2 " 178 - /> 179 - {/snippet} 180 - </Slider.Root> 181 - 182 - <span class="text-sm"> 183 - {formatTime(playback)}/{formatTime(songLength)} 184 - </span> 185 - 186 - <!-- <button 187 - class={cn("flex", "cursor-pointer", { "text-orange-500": shuffle })} 188 - onclick={() => (shuffle = !shuffle)} 189 - > 190 - <Shuffle /> 191 - </button> 192 - <button 193 - class={cn("flex", "cursor-pointer", { "text-orange-500": repeat })} 194 - onclick={() => (repeat = !repeat)} 195 - > 196 - <Repeat /> 197 - </button> --> 198 - </div> 199 - </div> 200 - {/if} 201 - </aside>
-1
apps/frontend/src/lib/index.ts
··· 1 - // place files you want to import through the `$lib` alias in this folder.
-15
apps/frontend/src/routes/+layout.svelte
··· 1 - <script lang="ts"> 2 - import Navbar from "$lib/components/Navbar.svelte"; 3 - import Player1 from "$lib/components/Player1.svelte"; 4 - import Player2 from "$lib/components/Player2.svelte"; 5 - import "../app.css"; 6 - 7 - let { children } = $props(); 8 - </script> 9 - 10 - <Navbar /> 11 - <main class="m-2 flex flex-col items-center"> 12 - {@render children()} 13 - </main> 14 - <!-- <Player1 /> --> 15 - <Player2 />
-32
apps/frontend/src/routes/+page.svelte
··· 1 - <section class="flex flex-col items-center gap-2 pt-10"> 2 - <header class="flex flex-col items-center"> 3 - <h1 class="text-5xl font-bold tracking-tighter text-orange-600">Comet</h1> 4 - <p class="flex flex-col items-center text-center text-lg"> 5 - Your music, on ATProto. 6 - </p> 7 - </header> 8 - 9 - <div> 10 - <h2 class="text-2xl font-bold tracking-tighter text-orange-600">Why?</h2> 11 - <ol class="list-disc"> 12 - <li> 13 - free yourself from Big Techโ„ข๏ธ; don't let them tell you what music you 14 - can and can't make 15 - </li> 16 - <li> 17 - Listen to music in full* quality, without being subject to horrendous 18 - sounding data compression. 19 - </li> 20 - <li> 21 - if I die, all your data is yours, and can be used by other projects! 22 - </li> 23 - <li> 24 - Integrate your playback with <span class="text-teal-800 underline"> 25 - teal.fm 26 - </span> 27 - and let everyone else know what you're listening to! 28 - </li> 29 - <li>borpa</li> 30 - </ol> 31 - </div> 32 - </section>
apps/frontend/static/favicon.png

This is a binary file and will not be displayed.

-18
apps/frontend/svelte.config.js
··· 1 - import adapter from "@sveltejs/adapter-auto"; 2 - import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; 3 - 4 - /** @type {import('@sveltejs/kit').Config} */ 5 - const config = { 6 - // Consult https://svelte.dev/docs/kit/integrations 7 - // for more information about preprocessors 8 - preprocess: vitePreprocess(), 9 - 10 - kit: { 11 - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 12 - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 - // See https://svelte.dev/docs/kit/adapters for more information about adapters. 14 - adapter: adapter(), 15 - }, 16 - }; 17 - 18 - export default config;
-19
apps/frontend/tsconfig.json
··· 1 - { 2 - "extends": "./.svelte-kit/tsconfig.json", 3 - "compilerOptions": { 4 - "allowJs": true, 5 - "checkJs": true, 6 - "esModuleInterop": true, 7 - "forceConsistentCasingInFileNames": true, 8 - "resolveJsonModule": true, 9 - "skipLibCheck": true, 10 - "sourceMap": true, 11 - "strict": true, 12 - "moduleResolution": "bundler" 13 - } 14 - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 15 - // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 16 - // 17 - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 - // from the referenced tsconfig.json - TypeScript does not merge them in 19 - }
-7
apps/frontend/vite.config.ts
··· 1 - import tailwindcss from "@tailwindcss/vite"; 2 - import { sveltekit } from "@sveltejs/kit/vite"; 3 - import { defineConfig } from "vite"; 4 - 5 - export default defineConfig({ 6 - plugins: [tailwindcss(), sveltekit()], 7 - });
+30
assets/css/app.css
··· 1 + /* See the Tailwind configuration guide for advanced usage 2 + https://tailwindcss.com/docs/configuration */ 3 + 4 + @import "tailwindcss" source(none); 5 + @source "../css"; 6 + @source "../js"; 7 + @source "../../lib/comet_app"; 8 + @source "../../lib/comet_web"; 9 + 10 + /* A Tailwind plugin that makes "hero-#{ICON}" classes available. 11 + The heroicons installation itself is managed by your mix.exs */ 12 + @plugin "../vendor/heroicons"; 13 + 14 + /* Add variants based on LiveView classes */ 15 + @custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &); 16 + @custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &); 17 + @custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &); 18 + 19 + /* Use the data attribute for dark mode */ 20 + @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); 21 + 22 + /* Make LiveView wrapper divs transparent for layout */ 23 + [data-phx-session], [data-phx-teleported-src] { display: contents } 24 + 25 + /* This file is for your main application CSS */ 26 + @theme { 27 + --color-brand-light: #ffa34e; 28 + --color-brand-dark: #ff4400; 29 + --font-sans: "Work Sans Variable", ui-sans-serif, system-ui, sans-serif; 30 + }
+84
assets/js/app.js
··· 1 + // If you want to use Phoenix channels, run `mix help phx.gen.channel` 2 + // to get started and then uncomment the line below. 3 + // import "./user_socket.js" 4 + 5 + // You can include dependencies in two ways. 6 + // 7 + // The simplest option is to put them in assets/vendor and 8 + // import them using relative paths: 9 + // 10 + // import "../vendor/some-package.js" 11 + // 12 + // Alternatively, you can `npm install some-package --prefix assets` and import 13 + // them using a path starting with the package name: 14 + // 15 + // import "some-package" 16 + // 17 + // If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file. 18 + // To load it, simply add a second `<link>` to your `root.html.heex` file. 19 + 20 + // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. 21 + import "phoenix_html" 22 + import "@fontsource-variable/work-sans" 23 + // Establish Phoenix Socket and LiveView configuration. 24 + import {Socket} from "phoenix" 25 + import {LiveSocket} from "phoenix_live_view" 26 + import {hooks as colocatedHooks} from "phoenix-colocated/comet" 27 + import topbar from "../vendor/topbar" 28 + 29 + const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") 30 + const liveSocket = new LiveSocket("/live", Socket, { 31 + longPollFallbackMs: 2500, 32 + params: {_csrf_token: csrfToken}, 33 + hooks: {...colocatedHooks}, 34 + }) 35 + 36 + // Show progress bar on live navigation and form submits 37 + topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) 38 + window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) 39 + window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) 40 + 41 + // connect if there are any LiveViews on the page 42 + liveSocket.connect() 43 + 44 + // expose liveSocket on window for web console debug logs and latency simulation: 45 + // >> liveSocket.enableDebug() 46 + // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session 47 + // >> liveSocket.disableLatencySim() 48 + window.liveSocket = liveSocket 49 + 50 + // The lines below enable quality of life phoenix_live_reload 51 + // development features: 52 + // 53 + // 1. stream server logs to the browser console 54 + // 2. click on elements to jump to their definitions in your code editor 55 + // 56 + if (process.env.NODE_ENV === "development") { 57 + window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => { 58 + // Enable server log streaming to client. 59 + // Disable with reloader.disableServerLogs() 60 + reloader.enableServerLogs() 61 + 62 + // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component 63 + // 64 + // * click with "c" key pressed to open at caller location 65 + // * click with "d" key pressed to open at function component definition location 66 + let keyDown 67 + window.addEventListener("keydown", e => keyDown = e.key) 68 + window.addEventListener("keyup", _e => keyDown = null) 69 + window.addEventListener("click", e => { 70 + if(keyDown === "c"){ 71 + e.preventDefault() 72 + e.stopImmediatePropagation() 73 + reloader.openEditorAtCaller(e.target) 74 + } else if(keyDown === "d"){ 75 + e.preventDefault() 76 + e.stopImmediatePropagation() 77 + reloader.openEditorAtDef(e.target) 78 + } 79 + }, true) 80 + 81 + window.liveReloader = reloader 82 + }) 83 + } 84 +
+32
assets/tsconfig.json
··· 1 + // This file is needed on most editors to enable the intelligent autocompletion 2 + // of LiveView's JavaScript API methods. You can safely delete it if you don't need it. 3 + // 4 + // Note: This file assumes a basic esbuild setup without node_modules. 5 + // We include a generic paths alias to deps to mimic how esbuild resolves 6 + // the Phoenix and LiveView JavaScript assets. 7 + // If you have a package.json in your project, you should remove the 8 + // paths configuration and instead add the phoenix dependencies to the 9 + // dependencies section of your package.json: 10 + // 11 + // { 12 + // ... 13 + // "dependencies": { 14 + // ..., 15 + // "phoenix": "../deps/phoenix", 16 + // "phoenix_html": "../deps/phoenix_html", 17 + // "phoenix_live_view": "../deps/phoenix_live_view" 18 + // } 19 + // } 20 + // 21 + // Feel free to adjust this configuration however you need. 22 + { 23 + "compilerOptions": { 24 + "baseUrl": ".", 25 + "paths": { 26 + "*": ["../deps/*"] 27 + }, 28 + "allowJs": true, 29 + "noEmit": true 30 + }, 31 + "include": ["js/**/*"] 32 + }
+43
assets/vendor/heroicons.js
··· 1 + const plugin = require("tailwindcss/plugin") 2 + const fs = require("fs") 3 + const path = require("path") 4 + 5 + module.exports = plugin(function({matchComponents, theme}) { 6 + let iconsDir = path.join(__dirname, "../../deps/heroicons/optimized") 7 + let values = {} 8 + let icons = [ 9 + ["", "/24/outline"], 10 + ["-solid", "/24/solid"], 11 + ["-mini", "/20/solid"], 12 + ["-micro", "/16/solid"] 13 + ] 14 + icons.forEach(([suffix, dir]) => { 15 + fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { 16 + let name = path.basename(file, ".svg") + suffix 17 + values[name] = {name, fullPath: path.join(iconsDir, dir, file)} 18 + }) 19 + }) 20 + matchComponents({ 21 + "hero": ({name, fullPath}) => { 22 + let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") 23 + content = encodeURIComponent(content) 24 + let size = theme("spacing.6") 25 + if (name.endsWith("-mini")) { 26 + size = theme("spacing.5") 27 + } else if (name.endsWith("-micro")) { 28 + size = theme("spacing.4") 29 + } 30 + return { 31 + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, 32 + "-webkit-mask": `var(--hero-${name})`, 33 + "mask": `var(--hero-${name})`, 34 + "mask-repeat": "no-repeat", 35 + "background-color": "currentColor", 36 + "vertical-align": "middle", 37 + "display": "inline-block", 38 + "width": size, 39 + "height": size 40 + } 41 + } 42 + }, {values}) 43 + })
+138
assets/vendor/topbar.js
··· 1 + /** 2 + * @license MIT 3 + * topbar 3.0.0 4 + * http://buunguyen.github.io/topbar 5 + * Copyright (c) 2024 Buu Nguyen 6 + */ 7 + (function (window, document) { 8 + "use strict"; 9 + 10 + var canvas, 11 + currentProgress, 12 + showing, 13 + progressTimerId = null, 14 + fadeTimerId = null, 15 + delayTimerId = null, 16 + addEvent = function (elem, type, handler) { 17 + if (elem.addEventListener) elem.addEventListener(type, handler, false); 18 + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); 19 + else elem["on" + type] = handler; 20 + }, 21 + options = { 22 + autoRun: true, 23 + barThickness: 3, 24 + barColors: { 25 + 0: "rgba(26, 188, 156, .9)", 26 + ".25": "rgba(52, 152, 219, .9)", 27 + ".50": "rgba(241, 196, 15, .9)", 28 + ".75": "rgba(230, 126, 34, .9)", 29 + "1.0": "rgba(211, 84, 0, .9)", 30 + }, 31 + shadowBlur: 10, 32 + shadowColor: "rgba(0, 0, 0, .6)", 33 + className: null, 34 + }, 35 + repaint = function () { 36 + canvas.width = window.innerWidth; 37 + canvas.height = options.barThickness * 5; // need space for shadow 38 + 39 + var ctx = canvas.getContext("2d"); 40 + ctx.shadowBlur = options.shadowBlur; 41 + ctx.shadowColor = options.shadowColor; 42 + 43 + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); 44 + for (var stop in options.barColors) 45 + lineGradient.addColorStop(stop, options.barColors[stop]); 46 + ctx.lineWidth = options.barThickness; 47 + ctx.beginPath(); 48 + ctx.moveTo(0, options.barThickness / 2); 49 + ctx.lineTo( 50 + Math.ceil(currentProgress * canvas.width), 51 + options.barThickness / 2 52 + ); 53 + ctx.strokeStyle = lineGradient; 54 + ctx.stroke(); 55 + }, 56 + createCanvas = function () { 57 + canvas = document.createElement("canvas"); 58 + var style = canvas.style; 59 + style.position = "fixed"; 60 + style.top = style.left = style.right = style.margin = style.padding = 0; 61 + style.zIndex = 100001; 62 + style.display = "none"; 63 + if (options.className) canvas.classList.add(options.className); 64 + addEvent(window, "resize", repaint); 65 + }, 66 + topbar = { 67 + config: function (opts) { 68 + for (var key in opts) 69 + if (options.hasOwnProperty(key)) options[key] = opts[key]; 70 + }, 71 + show: function (delay) { 72 + if (showing) return; 73 + if (delay) { 74 + if (delayTimerId) return; 75 + delayTimerId = setTimeout(() => topbar.show(), delay); 76 + } else { 77 + showing = true; 78 + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); 79 + if (!canvas) createCanvas(); 80 + if (!canvas.parentElement) document.body.appendChild(canvas); 81 + canvas.style.opacity = 1; 82 + canvas.style.display = "block"; 83 + topbar.progress(0); 84 + if (options.autoRun) { 85 + (function loop() { 86 + progressTimerId = window.requestAnimationFrame(loop); 87 + topbar.progress( 88 + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) 89 + ); 90 + })(); 91 + } 92 + } 93 + }, 94 + progress: function (to) { 95 + if (typeof to === "undefined") return currentProgress; 96 + if (typeof to === "string") { 97 + to = 98 + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 99 + ? currentProgress 100 + : 0) + parseFloat(to); 101 + } 102 + currentProgress = to > 1 ? 1 : to; 103 + repaint(); 104 + return currentProgress; 105 + }, 106 + hide: function () { 107 + clearTimeout(delayTimerId); 108 + delayTimerId = null; 109 + if (!showing) return; 110 + showing = false; 111 + if (progressTimerId != null) { 112 + window.cancelAnimationFrame(progressTimerId); 113 + progressTimerId = null; 114 + } 115 + (function loop() { 116 + if (topbar.progress("+.1") >= 1) { 117 + canvas.style.opacity -= 0.05; 118 + if (canvas.style.opacity <= 0.05) { 119 + canvas.style.display = "none"; 120 + fadeTimerId = null; 121 + return; 122 + } 123 + } 124 + fadeTimerId = window.requestAnimationFrame(loop); 125 + })(); 126 + }, 127 + }; 128 + 129 + if (typeof module === "object" && typeof module.exports === "object") { 130 + module.exports = topbar; 131 + } else if (typeof define === "function" && define.amd) { 132 + define(function () { 133 + return topbar; 134 + }); 135 + } else { 136 + this.topbar = topbar; 137 + } 138 + }.call(this, window, document));
-728
bun.lock
··· 1 - { 2 - "lockfileVersion": 1, 3 - "workspaces": { 4 - "": { 5 - "name": "comet", 6 - "devDependencies": { 7 - "@types/bun": "latest", 8 - "pino-pretty": "^13.0.0", 9 - "prettier": "^3.5.3", 10 - "prettier-plugin-svelte": "^3.4.0", 11 - "prettier-plugin-tailwindcss": "^0.6.11", 12 - }, 13 - "peerDependencies": { 14 - "typescript": "^5", 15 - }, 16 - }, 17 - "apps/frontend": { 18 - "name": "comet", 19 - "version": "0.0.1", 20 - "devDependencies": { 21 - "@eslint/compat": "^1.2.5", 22 - "@eslint/js": "^9.18.0", 23 - "@fontsource-variable/inter": "^5.2.5", 24 - "@lucide/svelte": "^0.487.0", 25 - "@sveltejs/adapter-auto": "^4.0.0", 26 - "@sveltejs/kit": "^2.16.0", 27 - "@sveltejs/vite-plugin-svelte": "^5.0.0", 28 - "@tailwindcss/vite": "^4.0.0", 29 - "bits-ui": "^1.3.17", 30 - "clsx": "^2.1.1", 31 - "eslint": "^9.18.0", 32 - "eslint-config-prettier": "^10.0.1", 33 - "eslint-plugin-svelte": "^3.0.0", 34 - "globals": "^16.0.0", 35 - "svelte": "^5.0.0", 36 - "svelte-check": "^4.0.0", 37 - "tailwindcss": "^4.0.0", 38 - "typescript": "^5.0.0", 39 - "typescript-eslint": "^8.20.0", 40 - "vite": "^6.2.5", 41 - }, 42 - }, 43 - "packages/lexicons": { 44 - "name": "@comet/lexicons", 45 - "dependencies": { 46 - "@atcute/lexicons": "^1.0.3", 47 - }, 48 - "devDependencies": { 49 - "@atcute/lex-cli": "^2.0.2", 50 - "@types/bun": "latest", 51 - }, 52 - "peerDependencies": { 53 - "typescript": "^5", 54 - }, 55 - }, 56 - "packages/test": { 57 - "name": "test", 58 - "dependencies": { 59 - "@atcute/atproto": "^3.0.2", 60 - "@atcute/bluesky": "^3.0.2", 61 - "@atcute/client": "^4.0.2", 62 - "@comet/lexicons": "workspace:*", 63 - "rambdax": "^11.3.1", 64 - }, 65 - "devDependencies": { 66 - "@types/bun": "latest", 67 - }, 68 - "peerDependencies": { 69 - "typescript": "^5", 70 - }, 71 - }, 72 - }, 73 - "packages": { 74 - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], 75 - 76 - "@atcute/atproto": ["@atcute/atproto@3.0.2", "", { "dependencies": { "@atcute/lexicons": "^1.0.2" } }, "sha512-p37GqTmrxc1XaxtX8JsePEuomL+PtDeGdy0lcBm+HisD03ZZTia7MouxUpnYezX0l926fFaDc9tllIBtX9iSsQ=="], 77 - 78 - "@atcute/bluesky": ["@atcute/bluesky@3.0.2", "", { "dependencies": { "@atcute/atproto": "^3.0.2", "@atcute/lexicons": "^1.0.2" } }, "sha512-xDRu/8Rlu3uTG/Mf625vUvKiFvy3hdCE371pXSJpHofivNZxi+MburdmYgOsBWZstNMo4vTBUviWaLJpL23rFg=="], 79 - 80 - "@atcute/client": ["@atcute/client@4.0.2", "", { "dependencies": { "@atcute/identity": "^1.0.2", "@atcute/lexicons": "^1.0.2" } }, "sha512-AOs6DEm59I0+wt8JOEOjKQLtBYZMML9p40pxaEWqP/ukAlhYUfLc9Geby5CMBmh7TchQCMCSHQzrY/lDs026Bw=="], 81 - 82 - "@atcute/identity": ["@atcute/identity@1.0.2", "", { "dependencies": { "@atcute/lexicons": "^1.0.2", "@badrap/valita": "^0.4.4" } }, "sha512-SrDPHuEarEHj9bx7NfYn7DYG6kIgJIMRU581iOCIaVaiZ1WhE9D8QxTxeYG/rbGNSa85E891ECp1sQcKiBN0kg=="], 83 - 84 - "@atcute/lex-cli": ["@atcute/lex-cli@2.0.2", "", { "dependencies": { "@atcute/lexicon-doc": "^1.0.1", "@badrap/valita": "^0.4.4", "@externdefs/collider": "^0.3.0", "picocolors": "^1.1.1", "prettier": "^3.5.3" }, "bin": { "lex-cli": "cli.mjs" } }, "sha512-GdudG2F4FKgFLdE7pB+aj2JOprCHkjlYtG1/rJriHKUZ5CPPfhd8A/+xMXaf9qoNqtyK4L7G3GYpd2PnS9bF8Q=="], 85 - 86 - "@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.0.1", "", { "dependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-BcuCN8aPXP1QfVAy6B6SXDiNMD0Whqsp4QhFShVpOdGjZIRbSxlRaTkY1rT3peNZy5zi1U5y1LlqGdoXa79wVg=="], 87 - 88 - "@atcute/lexicons": ["@atcute/lexicons@1.0.3", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-R4xa3AMD+uMNn67/Nly0ohieT+vuN2qeV8Oq/mkpb0O3pFTuG7IkhXEGIXVnFY6I/NEQGhWB1FjHYpgRyL35Pw=="], 89 - 90 - "@badrap/valita": ["@badrap/valita@0.4.4", "", {}, "sha512-GEhUCk9c4XbNxi+0YZHZsV4fYNd6HejfWuN4Ti4c02DauX+LyX5WY1Y3WfyZ8Pxxl0zqhs+MLtW98cMh86vv6g=="], 91 - 92 - "@comet/lexicons": ["@comet/lexicons@workspace:packages/lexicons"], 93 - 94 - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], 95 - 96 - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], 97 - 98 - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], 99 - 100 - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], 101 - 102 - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], 103 - 104 - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], 105 - 106 - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], 107 - 108 - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], 109 - 110 - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], 111 - 112 - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], 113 - 114 - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], 115 - 116 - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], 117 - 118 - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], 119 - 120 - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], 121 - 122 - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], 123 - 124 - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], 125 - 126 - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], 127 - 128 - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], 129 - 130 - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], 131 - 132 - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], 133 - 134 - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], 135 - 136 - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], 137 - 138 - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], 139 - 140 - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], 141 - 142 - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], 143 - 144 - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], 145 - 146 - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], 147 - 148 - "@eslint/compat": ["@eslint/compat@1.2.9", "", { "peerDependencies": { "eslint": "^9.10.0" }, "optionalPeers": ["eslint"] }, "sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA=="], 149 - 150 - "@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="], 151 - 152 - "@eslint/config-helpers": ["@eslint/config-helpers@0.2.2", "", {}, "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg=="], 153 - 154 - "@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="], 155 - 156 - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], 157 - 158 - "@eslint/js": ["@eslint/js@9.27.0", "", {}, "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA=="], 159 - 160 - "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], 161 - 162 - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="], 163 - 164 - "@externdefs/collider": ["@externdefs/collider@0.3.0", "", { "peerDependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q=="], 165 - 166 - "@floating-ui/core": ["@floating-ui/core@1.7.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA=="], 167 - 168 - "@floating-ui/dom": ["@floating-ui/dom@1.7.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg=="], 169 - 170 - "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], 171 - 172 - "@fontsource-variable/inter": ["@fontsource-variable/inter@5.2.5", "", {}, "sha512-TrWffUAFOnT8zroE9YmGybagoOgM/HjRqMQ8k9R0vVgXlnUh/vnpbGPAS/Caz1KIlOPnPGh6fvJbb7DHbFCncA=="], 173 - 174 - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 175 - 176 - "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], 177 - 178 - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 179 - 180 - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 181 - 182 - "@internationalized/date": ["@internationalized/date@3.8.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw=="], 183 - 184 - "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], 185 - 186 - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], 187 - 188 - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 189 - 190 - "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], 191 - 192 - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], 193 - 194 - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], 195 - 196 - "@lucide/svelte": ["@lucide/svelte@0.487.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-27b/wUzWrqDJu97+1iSV2X8L2JGRWH/mAWAjHgazWxhGxVu/kS0p3SbNu6w3skNmQNEku33EKU1v44IVwULzbw=="], 197 - 198 - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 199 - 200 - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 201 - 202 - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 203 - 204 - "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], 205 - 206 - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="], 207 - 208 - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.2", "", { "os": "android", "cpu": "arm64" }, "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw=="], 209 - 210 - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w=="], 211 - 212 - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ=="], 213 - 214 - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ=="], 215 - 216 - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q=="], 217 - 218 - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.2", "", { "os": "linux", "cpu": "arm" }, "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q=="], 219 - 220 - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.2", "", { "os": "linux", "cpu": "arm" }, "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg=="], 221 - 222 - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg=="], 223 - 224 - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg=="], 225 - 226 - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw=="], 227 - 228 - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q=="], 229 - 230 - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg=="], 231 - 232 - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg=="], 233 - 234 - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ=="], 235 - 236 - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.2", "", { "os": "linux", "cpu": "x64" }, "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng=="], 237 - 238 - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.2", "", { "os": "linux", "cpu": "x64" }, "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA=="], 239 - 240 - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg=="], 241 - 242 - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA=="], 243 - 244 - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.2", "", { "os": "win32", "cpu": "x64" }, "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA=="], 245 - 246 - "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], 247 - 248 - "@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@4.0.0", "", { "dependencies": { "import-meta-resolve": "^4.1.0" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ=="], 249 - 250 - "@sveltejs/kit": ["@sveltejs/kit@2.21.0", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-kvu4h9qXduiPk1Q1oqFKDLFGu/7mslEYbVaqpbBcBxjlRJnvNCFwEvEwKt0Mx9TtSi8J77xRelvJobrGlst4nQ=="], 251 - 252 - "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.0.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.0", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.15", "vitefu": "^1.0.4" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw=="], 253 - 254 - "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="], 255 - 256 - "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], 257 - 258 - "@tailwindcss/node": ["@tailwindcss/node@4.1.7", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.7" } }, "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g=="], 259 - 260 - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.7", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.7", "@tailwindcss/oxide-darwin-arm64": "4.1.7", "@tailwindcss/oxide-darwin-x64": "4.1.7", "@tailwindcss/oxide-freebsd-x64": "4.1.7", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", "@tailwindcss/oxide-linux-x64-musl": "4.1.7", "@tailwindcss/oxide-wasm32-wasi": "4.1.7", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" } }, "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ=="], 261 - 262 - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.7", "", { "os": "android", "cpu": "arm64" }, "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg=="], 263 - 264 - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg=="], 265 - 266 - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw=="], 267 - 268 - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw=="], 269 - 270 - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7", "", { "os": "linux", "cpu": "arm" }, "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g=="], 271 - 272 - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA=="], 273 - 274 - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A=="], 275 - 276 - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg=="], 277 - 278 - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA=="], 279 - 280 - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.7", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.9", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A=="], 281 - 282 - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw=="], 283 - 284 - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ=="], 285 - 286 - "@tailwindcss/vite": ["@tailwindcss/vite@4.1.7", "", { "dependencies": { "@tailwindcss/node": "4.1.7", "@tailwindcss/oxide": "4.1.7", "tailwindcss": "4.1.7" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-tYa2fO3zDe41I7WqijyVbRd8oWT0aEID1Eokz5hMT6wShLIHj3yvwj9XbfuloHP9glZ6H+aG2AN/+ZrxJ1Y5RQ=="], 287 - 288 - "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], 289 - 290 - "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], 291 - 292 - "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], 293 - 294 - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 295 - 296 - "@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="], 297 - 298 - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/type-utils": "8.32.1", "@typescript-eslint/utils": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg=="], 299 - 300 - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg=="], 301 - 302 - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1" } }, "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA=="], 303 - 304 - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.32.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA=="], 305 - 306 - "@typescript-eslint/types": ["@typescript-eslint/types@8.32.1", "", {}, "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="], 307 - 308 - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg=="], 309 - 310 - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.32.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA=="], 311 - 312 - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w=="], 313 - 314 - "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], 315 - 316 - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 317 - 318 - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 319 - 320 - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 321 - 322 - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 323 - 324 - "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], 325 - 326 - "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], 327 - 328 - "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], 329 - 330 - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 331 - 332 - "bits-ui": ["bits-ui@1.4.8", "", { "dependencies": { "@floating-ui/core": "^1.6.4", "@floating-ui/dom": "^1.6.7", "@internationalized/date": "^3.5.6", "esm-env": "^1.1.2", "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1", "tabbable": "^6.2.0" }, "peerDependencies": { "svelte": "^5.11.0" } }, "sha512-j34GsdSsJ+ZBl9h/70VkufvrlEgTKQSZvm80eM5VvuhLJWvpfEpn9+k0FVmtDQl9NSPgEVtI9imYhm8nW9Nj/w=="], 333 - 334 - "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], 335 - 336 - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 337 - 338 - "bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], 339 - 340 - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 341 - 342 - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 343 - 344 - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], 345 - 346 - "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], 347 - 348 - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], 349 - 350 - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 351 - 352 - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 353 - 354 - "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], 355 - 356 - "comet": ["comet@workspace:apps/frontend"], 357 - 358 - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 359 - 360 - "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], 361 - 362 - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 363 - 364 - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], 365 - 366 - "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], 367 - 368 - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], 369 - 370 - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 371 - 372 - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], 373 - 374 - "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], 375 - 376 - "devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="], 377 - 378 - "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], 379 - 380 - "enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="], 381 - 382 - "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], 383 - 384 - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 385 - 386 - "eslint": ["eslint@9.27.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.27.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q=="], 387 - 388 - "eslint-config-prettier": ["eslint-config-prettier@10.1.5", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw=="], 389 - 390 - "eslint-plugin-svelte": ["eslint-plugin-svelte@3.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.6.1", "@jridgewell/sourcemap-codec": "^1.5.0", "esutils": "^2.0.3", "globals": "^16.0.0", "known-css-properties": "^0.36.0", "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^7.0.0", "semver": "^7.6.3", "svelte-eslint-parser": "^1.2.0" }, "peerDependencies": { "eslint": "^8.57.1 || ^9.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-w6NQifz1xBIFcs4XMLYT36xukgN1xgzoPGk1nJLC1UeZ8O4CUCCSZx1tgwq3yRNkMSXMUqbMJjslFweWsDlAoA=="], 391 - 392 - "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="], 393 - 394 - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], 395 - 396 - "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 397 - 398 - "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], 399 - 400 - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 401 - 402 - "esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="], 403 - 404 - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 405 - 406 - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 407 - 408 - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 409 - 410 - "fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="], 411 - 412 - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 413 - 414 - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 415 - 416 - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 417 - 418 - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 419 - 420 - "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], 421 - 422 - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], 423 - 424 - "fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], 425 - 426 - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 427 - 428 - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 429 - 430 - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 431 - 432 - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 433 - 434 - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 435 - 436 - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 437 - 438 - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 439 - 440 - "globals": ["globals@16.1.0", "", {}, "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g=="], 441 - 442 - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 443 - 444 - "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 445 - 446 - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 447 - 448 - "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], 449 - 450 - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 451 - 452 - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 453 - 454 - "import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="], 455 - 456 - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 457 - 458 - "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], 459 - 460 - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 461 - 462 - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 463 - 464 - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 465 - 466 - "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], 467 - 468 - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 469 - 470 - "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], 471 - 472 - "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], 473 - 474 - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], 475 - 476 - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 477 - 478 - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 479 - 480 - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 481 - 482 - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 483 - 484 - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], 485 - 486 - "known-css-properties": ["known-css-properties@0.36.0", "", {}, "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA=="], 487 - 488 - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 489 - 490 - "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], 491 - 492 - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], 493 - 494 - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], 495 - 496 - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], 497 - 498 - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], 499 - 500 - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], 501 - 502 - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], 503 - 504 - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], 505 - 506 - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], 507 - 508 - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], 509 - 510 - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], 511 - 512 - "lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], 513 - 514 - "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], 515 - 516 - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 517 - 518 - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 519 - 520 - "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], 521 - 522 - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 523 - 524 - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 525 - 526 - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 527 - 528 - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 529 - 530 - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], 531 - 532 - "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], 533 - 534 - "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], 535 - 536 - "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], 537 - 538 - "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], 539 - 540 - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 541 - 542 - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 543 - 544 - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 545 - 546 - "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], 547 - 548 - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 549 - 550 - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 551 - 552 - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 553 - 554 - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 555 - 556 - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 557 - 558 - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 559 - 560 - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 561 - 562 - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 563 - 564 - "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], 565 - 566 - "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], 567 - 568 - "pino-pretty": ["pino-pretty@13.0.0", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^3.0.2", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pump": "^3.0.0", "secure-json-parse": "^2.4.0", "sonic-boom": "^4.0.1", "strip-json-comments": "^3.1.1" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA=="], 569 - 570 - "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], 571 - 572 - "postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="], 573 - 574 - "postcss-safe-parser": ["postcss-safe-parser@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A=="], 575 - 576 - "postcss-scss": ["postcss-scss@4.0.9", "", { "peerDependencies": { "postcss": "^8.4.29" } }, "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A=="], 577 - 578 - "postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="], 579 - 580 - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 581 - 582 - "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], 583 - 584 - "prettier-plugin-svelte": ["prettier-plugin-svelte@3.4.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ=="], 585 - 586 - "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="], 587 - 588 - "pump": ["pump@3.0.2", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="], 589 - 590 - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 591 - 592 - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 593 - 594 - "rambdax": ["rambdax@11.3.1", "", {}, "sha512-ecsDpTQZuzZD16hPGpkja3klaho4I0tRp5IkjmUUrR7tNnw5RP9K/eiPfHev4HrRNr4OoUetIL/OOWFmeYls7A=="], 595 - 596 - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], 597 - 598 - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 599 - 600 - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 601 - 602 - "rollup": ["rollup@4.40.2", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.2", "@rollup/rollup-android-arm64": "4.40.2", "@rollup/rollup-darwin-arm64": "4.40.2", "@rollup/rollup-darwin-x64": "4.40.2", "@rollup/rollup-freebsd-arm64": "4.40.2", "@rollup/rollup-freebsd-x64": "4.40.2", "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", "@rollup/rollup-linux-arm-musleabihf": "4.40.2", "@rollup/rollup-linux-arm64-gnu": "4.40.2", "@rollup/rollup-linux-arm64-musl": "4.40.2", "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-musl": "4.40.2", "@rollup/rollup-linux-s390x-gnu": "4.40.2", "@rollup/rollup-linux-x64-gnu": "4.40.2", "@rollup/rollup-linux-x64-musl": "4.40.2", "@rollup/rollup-win32-arm64-msvc": "4.40.2", "@rollup/rollup-win32-ia32-msvc": "4.40.2", "@rollup/rollup-win32-x64-msvc": "4.40.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg=="], 603 - 604 - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 605 - 606 - "runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="], 607 - 608 - "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], 609 - 610 - "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], 611 - 612 - "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], 613 - 614 - "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], 615 - 616 - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 617 - 618 - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 619 - 620 - "sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="], 621 - 622 - "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], 623 - 624 - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 625 - 626 - "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], 627 - 628 - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 629 - 630 - "style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="], 631 - 632 - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 633 - 634 - "svelte": ["svelte@5.30.2", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-zfGFEwwPeILToOxOqQyFq/vc8euXrX2XyoffkBNgn/k8D1nxbLt5+mNaqQBmZF/vVhBGmkY6VmNK18p9Gf0auQ=="], 635 - 636 - "svelte-check": ["svelte-check@4.2.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e49SU1RStvQhoipkQ/aonDhHnG3qxHSBtNfBRb9pxVXoa+N7qybAo32KgA9wEb2PCYFNaDg7bZCdhLD1vHpdYA=="], 637 - 638 - "svelte-eslint-parser": ["svelte-eslint-parser@1.2.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw=="], 639 - 640 - "svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], 641 - 642 - "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], 643 - 644 - "tailwindcss": ["tailwindcss@4.1.7", "", {}, "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg=="], 645 - 646 - "tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="], 647 - 648 - "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], 649 - 650 - "test": ["test@workspace:packages/test"], 651 - 652 - "tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], 653 - 654 - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 655 - 656 - "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], 657 - 658 - "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 659 - 660 - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 661 - 662 - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 663 - 664 - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 665 - 666 - "typescript-eslint": ["typescript-eslint@8.32.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.32.1", "@typescript-eslint/parser": "8.32.1", "@typescript-eslint/utils": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg=="], 667 - 668 - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 669 - 670 - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 671 - 672 - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 673 - 674 - "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], 675 - 676 - "vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="], 677 - 678 - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 679 - 680 - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 681 - 682 - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 683 - 684 - "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], 685 - 686 - "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], 687 - 688 - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 689 - 690 - "zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], 691 - 692 - "@comet/lexicons/@types/bun": ["@types/bun@1.2.14", "", { "dependencies": { "bun-types": "1.2.14" } }, "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q=="], 693 - 694 - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 695 - 696 - "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 697 - 698 - "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], 699 - 700 - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], 701 - 702 - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], 703 - 704 - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="], 705 - 706 - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.10", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" }, "bundled": true }, "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ=="], 707 - 708 - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="], 709 - 710 - "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 711 - 712 - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="], 713 - 714 - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 715 - 716 - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 717 - 718 - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 719 - 720 - "test/@types/bun": ["@types/bun@1.2.14", "", { "dependencies": { "bun-types": "1.2.14" } }, "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q=="], 721 - 722 - "@comet/lexicons/@types/bun/bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="], 723 - 724 - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], 725 - 726 - "test/@types/bun/bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="], 727 - } 728 - }
+71
config/config.exs
··· 1 + # This file is responsible for configuring your application 2 + # and its dependencies with the aid of the Config module. 3 + # 4 + # This configuration file is loaded before any dependency and 5 + # is restricted to this project. 6 + 7 + # General application configuration 8 + import Config 9 + 10 + config :comet, 11 + ecto_repos: [Comet.Repo], 12 + generators: [timestamp_type: :utc_datetime, binary_id: true] 13 + 14 + # Configure the endpoint 15 + config :comet, CometWeb.Endpoint, 16 + url: [host: "localhost"], 17 + adapter: Bandit.PhoenixAdapter, 18 + render_errors: [ 19 + formats: [html: CometWeb.ErrorHTML, json: CometWeb.ErrorJSON], 20 + layout: false 21 + ], 22 + pubsub_server: Comet.PubSub, 23 + live_view: [signing_salt: "ObastmTN"] 24 + 25 + # Configure the mailer 26 + # 27 + # By default it uses the "Local" adapter which stores the emails 28 + # locally. You can see the emails in your browser, at "/dev/mailbox". 29 + # 30 + # For production it's recommended to configure a different adapter 31 + # at the `config/runtime.exs`. 32 + config :comet, Comet.Mailer, adapter: Swoosh.Adapters.Local 33 + 34 + # Configure esbuild (the version is required) 35 + config :esbuild, 36 + version: "0.25.4", 37 + comet: [ 38 + args: 39 + ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --loader:.woff2=file --alias:@=.), 40 + cd: Path.expand("../assets", __DIR__), 41 + env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]} 42 + ] 43 + 44 + # Configure tailwind (the version is required) 45 + config :tailwind, 46 + version: "4.1.12", 47 + comet: [ 48 + args: ~w( 49 + --input=assets/css/app.css 50 + --output=priv/static/assets/css/app.css 51 + ), 52 + cd: Path.expand("..", __DIR__) 53 + ], 54 + version_check: false, 55 + path: 56 + System.get_env( 57 + "TAILWINDCSS_PATH", 58 + Path.expand("../assets/node_modules/.bin/tailwindcss", __DIR__) 59 + ) 60 + 61 + # Configure Elixir's Logger 62 + config :logger, :default_formatter, 63 + format: "$time $metadata[$level] $message\n", 64 + metadata: [:request_id] 65 + 66 + # Use Jason for JSON parsing in Phoenix 67 + config :phoenix, :json_library, Jason 68 + 69 + # Import environment specific config. This must remain at the bottom 70 + # of this file so it overrides the configuration defined above. 71 + import_config "#{config_env()}.exs"
+92
config/dev.exs
··· 1 + import Config 2 + 3 + # Configure your database 4 + config :comet, Comet.Repo, 5 + username: "comet", 6 + password: "comet", 7 + hostname: "localhost", 8 + database: "comet_dev", 9 + stacktrace: true, 10 + show_sensitive_data_on_connection_error: true, 11 + pool_size: 10 12 + 13 + # For development, we disable any cache and enable 14 + # debugging and code reloading. 15 + # 16 + # The watchers configuration can be used to run external 17 + # watchers to your application. For example, we can use it 18 + # to bundle .js and .css sources. 19 + config :comet, CometWeb.Endpoint, 20 + # Binding to loopback ipv4 address prevents access from other machines. 21 + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 22 + http: [ip: {127, 0, 0, 1}], 23 + check_origin: false, 24 + code_reloader: true, 25 + debug_errors: true, 26 + secret_key_base: "qEVbyaHJ1+52Mfh7Kfoc94yEwNc/e5vkRDUcAR4b3DofYJg7LgUjm4kd+3u+RelM", 27 + watchers: [ 28 + esbuild: {Esbuild, :install_and_run, [:comet, ~w(--sourcemap=inline --watch)]}, 29 + tailwind: {Tailwind, :install_and_run, [:comet, ~w(--watch)]} 30 + ] 31 + 32 + # ## SSL Support 33 + # 34 + # In order to use HTTPS in development, a self-signed 35 + # certificate can be generated by running the following 36 + # Mix task: 37 + # 38 + # mix phx.gen.cert 39 + # 40 + # Run `mix help phx.gen.cert` for more information. 41 + # 42 + # The `http:` config above can be replaced with: 43 + # 44 + # https: [ 45 + # port: 4001, 46 + # cipher_suite: :strong, 47 + # keyfile: "priv/cert/selfsigned_key.pem", 48 + # certfile: "priv/cert/selfsigned.pem" 49 + # ], 50 + # 51 + # If desired, both `http:` and `https:` keys can be 52 + # configured to run both http and https servers on 53 + # different ports. 54 + 55 + # Reload browser tabs when matching files change. 56 + config :comet, CometWeb.Endpoint, 57 + live_reload: [ 58 + web_console_logger: true, 59 + patterns: [ 60 + # Static assets, except user uploads 61 + ~r"priv/static/(?!uploads/).*\.(js|css|png|jpeg|jpg|gif|svg)$", 62 + # Gettext translations 63 + ~r"priv/gettext/.*\.po$", 64 + # Router, Controllers, LiveViews and LiveComponents 65 + ~r"lib/comet_web/router\.ex$", 66 + ~r"lib/comet_web/(controllers|live|components)/.*\.(ex|heex)$" 67 + ] 68 + ] 69 + 70 + # Enable dev routes for dashboard and mailbox 71 + config :comet, dev_routes: true 72 + 73 + # Do not include metadata nor timestamps in development logs 74 + config :logger, :default_formatter, format: "[$level] $message\n" 75 + 76 + # Set a higher stacktrace during development. Avoid configuring such 77 + # in production as building large stacktraces may be expensive. 78 + config :phoenix, :stacktrace_depth, 20 79 + 80 + # Initialize plugs at runtime for faster development compilation 81 + config :phoenix, :plug_init_mode, :runtime 82 + 83 + config :phoenix_live_view, 84 + # Include debug annotations and locations in rendered markup. 85 + # Changing this configuration will require mix clean and a full recompile. 86 + debug_heex_annotations: true, 87 + debug_attributes: true, 88 + # Enable helpful, but potentially expensive runtime checks 89 + enable_expensive_runtime_checks: true 90 + 91 + # Disable swoosh api client as it is only required for production adapters. 92 + config :swoosh, :api_client, false
+24
config/prod.exs
··· 1 + import Config 2 + 3 + # Note we also include the path to a cache manifest 4 + # containing the digested version of static files. This 5 + # manifest is generated by the `mix assets.deploy` task, 6 + # which you should run after static files are built and 7 + # before starting your production server. 8 + config :comet, CometWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" 9 + 10 + # Force using SSL in production. This also sets the "strict-security-transport" header, 11 + # also known as HSTS. `:force_ssl` is required to be set at compile-time. 12 + config :comet, CometWeb.Endpoint, force_ssl: [rewrite_on: [:x_forwarded_proto]] 13 + 14 + # Configure Swoosh API Client 15 + config :swoosh, api_client: Swoosh.ApiClient.Req 16 + 17 + # Disable Swoosh Local Memory Storage 18 + config :swoosh, local: false 19 + 20 + # Do not print debug messages in production 21 + config :logger, level: :info 22 + 23 + # Runtime production configuration, including reading 24 + # of environment variables, is done on config/runtime.exs.
+119
config/runtime.exs
··· 1 + import Config 2 + 3 + # config/runtime.exs is executed for all environments, including 4 + # during releases. It is executed after compilation and before the 5 + # system starts, so it is typically used to load production configuration 6 + # and secrets from environment variables or elsewhere. Do not define 7 + # any compile-time configuration in here, as it won't be applied. 8 + # The block below contains prod specific runtime configuration. 9 + 10 + # ## Using releases 11 + # 12 + # If you use `mix release`, you need to explicitly enable the server 13 + # by passing the PHX_SERVER=true when you start it: 14 + # 15 + # PHX_SERVER=true bin/comet start 16 + # 17 + # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 + # script that automatically sets the env var above. 19 + if System.get_env("PHX_SERVER") do 20 + config :comet, CometWeb.Endpoint, server: true 21 + end 22 + 23 + config :comet, CometWeb.Endpoint, http: [port: String.to_integer(System.get_env("PORT", "4000"))] 24 + 25 + if config_env() == :prod do 26 + database_url = 27 + System.get_env("DATABASE_URL") || 28 + raise """ 29 + environment variable DATABASE_URL is missing. 30 + For example: ecto://USER:PASS@HOST/DATABASE 31 + """ 32 + 33 + maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: [] 34 + 35 + config :comet, Comet.Repo, 36 + # ssl: true, 37 + url: database_url, 38 + pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), 39 + # For machines with several cores, consider starting multiple pools of `pool_size` 40 + # pool_count: 4, 41 + socket_options: maybe_ipv6 42 + 43 + # The secret key base is used to sign/encrypt cookies and other secrets. 44 + # A default value is used in config/dev.exs and config/test.exs but you 45 + # want to use a different value for prod and you most likely don't want 46 + # to check this value into version control, so we use an environment 47 + # variable instead. 48 + secret_key_base = 49 + System.get_env("SECRET_KEY_BASE") || 50 + raise """ 51 + environment variable SECRET_KEY_BASE is missing. 52 + You can generate one by calling: mix phx.gen.secret 53 + """ 54 + 55 + host = System.get_env("PHX_HOST") || "example.com" 56 + 57 + config :comet, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") 58 + 59 + config :comet, CometWeb.Endpoint, 60 + url: [host: host, port: 443, scheme: "https"], 61 + http: [ 62 + # Enable IPv6 and bind on all interfaces. 63 + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 64 + # See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0 65 + # for details about using IPv6 vs IPv4 and loopback vs public addresses. 66 + ip: {0, 0, 0, 0, 0, 0, 0, 0} 67 + ], 68 + secret_key_base: secret_key_base 69 + 70 + # ## SSL Support 71 + # 72 + # To get SSL working, you will need to add the `https` key 73 + # to your endpoint configuration: 74 + # 75 + # config :comet, CometWeb.Endpoint, 76 + # https: [ 77 + # ..., 78 + # port: 443, 79 + # cipher_suite: :strong, 80 + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 81 + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 82 + # ] 83 + # 84 + # The `cipher_suite` is set to `:strong` to support only the 85 + # latest and more secure SSL ciphers. This means old browsers 86 + # and clients may not be supported. You can set it to 87 + # `:compatible` for wider support. 88 + # 89 + # `:keyfile` and `:certfile` expect an absolute path to the key 90 + # and cert in disk or a relative path inside priv, for example 91 + # "priv/ssl/server.key". For all supported SSL configuration 92 + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 93 + # 94 + # We also recommend setting `force_ssl` in your config/prod.exs, 95 + # ensuring no data is ever sent via http, always redirecting to https: 96 + # 97 + # config :comet, CometWeb.Endpoint, 98 + # force_ssl: [hsts: true] 99 + # 100 + # Check `Plug.SSL` for all available options in `force_ssl`. 101 + 102 + # ## Configuring the mailer 103 + # 104 + # In production you need to configure the mailer to use a different adapter. 105 + # Here is an example configuration for Mailgun: 106 + # 107 + # config :comet, Comet.Mailer, 108 + # adapter: Swoosh.Adapters.Mailgun, 109 + # api_key: System.get_env("MAILGUN_API_KEY"), 110 + # domain: System.get_env("MAILGUN_DOMAIN") 111 + # 112 + # Most non-SMTP adapters require an API client. Swoosh supports Req, Hackney, 113 + # and Finch out-of-the-box. This configuration is typically done at 114 + # compile-time in your config/prod.exs: 115 + # 116 + # config :swoosh, :api_client, Swoosh.ApiClient.Req 117 + # 118 + # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. 119 + end
+37
config/test.exs
··· 1 + import Config 2 + 3 + # Configure your database 4 + # 5 + # The MIX_TEST_PARTITION environment variable can be used 6 + # to provide built-in test partitioning in CI environment. 7 + # Run `mix help test` for more information. 8 + config :comet, Comet.Repo, 9 + username: "postgres", 10 + password: "postgres", 11 + hostname: "localhost", 12 + database: "comet_test#{System.get_env("MIX_TEST_PARTITION")}", 13 + pool: Ecto.Adapters.SQL.Sandbox, 14 + pool_size: System.schedulers_online() * 2 15 + 16 + # We don't run a server during test. If one is required, 17 + # you can enable the server option below. 18 + config :comet, CometWeb.Endpoint, 19 + http: [ip: {127, 0, 0, 1}, port: 4002], 20 + secret_key_base: "p9+pymZBaKsKSlOfPLjw9HWpolaQwJSmVaepPAdfGpv3YUp/BO5SHkaS+Faavmec", 21 + server: false 22 + 23 + # In test we don't send emails 24 + config :comet, Comet.Mailer, adapter: Swoosh.Adapters.Test 25 + 26 + # Disable swoosh api client as it is only required for production adapters 27 + config :swoosh, :api_client, false 28 + 29 + # Print only warnings and errors during test 30 + config :logger, level: :warning 31 + 32 + # Initialize plugs at runtime for faster test compilation 33 + config :phoenix, :plug_init_mode, :runtime 34 + 35 + # Enable helpful, but potentially expensive runtime checks 36 + config :phoenix_live_view, 37 + enable_expensive_runtime_checks: true
+8 -3
flake.nix
··· 14 14 defaultForSystems = fn: forSystems (pkgs: {default = fn pkgs;}); 15 15 in { 16 16 devShells = defaultForSystems (pkgs: 17 - pkgs.mkShell { 18 - nativeBuildInputs = with pkgs; [nodejs_22 bun elixir erlang]; 19 - }); 17 + with pkgs; 18 + mkShell { 19 + nativeBuildInputs = [elixir erlang nodejs pnpm tailwindcss_4 watchman] ++ (lib.optional stdenv.isLinux [inotify-tools]); 20 + 21 + shellHook = '' 22 + export TAILWINDCSS_PATH="${lib.getExe tailwindcss_4}" 23 + ''; 24 + }); 20 25 }; 21 26 }
+28
lexicons/sh/comet/v0/actor/getProfile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.actor.getProfile", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get the profile view of an actor.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "Handle or DID of account to fetch profile of." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "ref", 23 + "ref": "sh.comet.v0.actor.profile#view" 24 + } 25 + } 26 + } 27 + } 28 + }
+37
lexicons/sh/comet/v0/actor/getProfiles.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.actor.getProfiles", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get the profile views of multiple actors.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actors"], 11 + "properties": { 12 + "actors": { 13 + "type": "array", 14 + "maxLength": 25, 15 + "items": { "type": "string", "format": "at-identifier" } 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["profiles"], 24 + "properties": { 25 + "profiles": { 26 + "type": "array", 27 + "items": { 28 + "type": "ref", 29 + "ref": "sh.comet.v0.actor.profile#view" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+109
lexicons/sh/comet/v0/actor/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A user's Comet profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "properties": { 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "description": "Free-form profile description text.", 20 + "maxGraphemes": 256, 21 + "maxLength": 2560 22 + }, 23 + "descriptionFacets": { 24 + "type": "ref", 25 + "description": "Annotations of the user's description.", 26 + "ref": "sh.comet.v0.richtext.facet" 27 + }, 28 + "avatar": { 29 + "type": "blob", 30 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", 31 + "accept": ["image/png", "image/jpeg"], 32 + "maxSize": 1000000 33 + }, 34 + "banner": { 35 + "type": "blob", 36 + "description": "Larger horizontal image to display behind profile view.", 37 + "accept": ["image/png", "image/jpeg"], 38 + "maxSize": 1000000 39 + }, 40 + "featuredItems": { 41 + "type": "array", 42 + "description": "Pinned items to be shown first on the user's profile.", 43 + "maxLength": 5, 44 + "items": { "type": "string", "format": "at-uri" } 45 + }, 46 + "createdAt": { "type": "string", "format": "datetime" } 47 + } 48 + } 49 + }, 50 + "view": { 51 + "type": "object", 52 + "required": ["did", "handle"], 53 + "properties": { 54 + "did": { "type": "string", "format": "did" }, 55 + "handle": { "type": "string", "format": "handle" }, 56 + "displayName": { 57 + "type": "string", 58 + "maxGraphemes": 64, 59 + "maxLength": 640 60 + }, 61 + "avatar": { "type": "string", "format": "uri" }, 62 + "indexedAt": { "type": "string", "format": "datetime" }, 63 + "createdAt": { "type": "string", "format": "datetime" }, 64 + "viewer": { "type": "ref", "ref": "#viewerState" } 65 + } 66 + }, 67 + "viewFull": { 68 + "type": "object", 69 + "required": ["did", "handle"], 70 + "properties": { 71 + "did": { "type": "string", "format": "did" }, 72 + "handle": { "type": "string", "format": "handle" }, 73 + "displayName": { 74 + "type": "string", 75 + "maxGraphemes": 64, 76 + "maxLength": 640 77 + }, 78 + "description": { 79 + "type": "string", 80 + "maxGraphemes": 256, 81 + "maxLength": 2560 82 + }, 83 + "descriptionFacets": { 84 + "type": "ref", 85 + "ref": "sh.comet.v0.richtext.facet" 86 + }, 87 + "avatar": { "type": "string", "format": "uri" }, 88 + "banner": { "type": "string", "format": "uri" }, 89 + "followersCount": { "type": "integer" }, 90 + "followsCount": { "type": "integer" }, 91 + "tracksCount": { "type": "integer" }, 92 + "playlistsCount": { "type": "integer" }, 93 + "indexedAt": { "type": "string", "format": "datetime" }, 94 + "createdAt": { "type": "string", "format": "datetime" }, 95 + "featuredItems": { 96 + "type": "array", 97 + "maxLength": 5, 98 + "items": { "type": "string", "format": "at-uri" } 99 + }, 100 + "viewer": { "type": "ref", "ref": "#viewerState" } 101 + } 102 + }, 103 + "viewerState": { 104 + "type": "object", 105 + "description": "Metadata about the requesting account's relationship with the user. TODO: determine if we create our own graph or inherit bsky's.", 106 + "properties": {} 107 + } 108 + } 109 + }
+47
lexicons/sh/comet/v0/feed/comment.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.comment", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A comment on a piece of Comet media.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["text", "subject", "createdAt"], 12 + "properties": { 13 + "text": { 14 + "type": "string", 15 + "minLength": 1, 16 + "minGraphemes": 1, 17 + "maxLength": 3000, 18 + "maxGraphemes": 300, 19 + "description": "The comment's content." 20 + }, 21 + "subject": { 22 + "type": "string", 23 + "format": "at-uri", 24 + "description": "The piece of media being commented on." 25 + }, 26 + "reply": { 27 + "type": "string", 28 + "format": "at-uri", 29 + "description": "Another sh.comet.v0.feed.comment this is in reply to." 30 + }, 31 + "langs": { 32 + "type": "array", 33 + "description": "Indicates human language of the comment's text content.", 34 + "maxLength": 3, 35 + "items": { "type": "string", "format": "language" } 36 + }, 37 + "facets": { 38 + "type": "array", 39 + "description": "Annotations of text (mentions, URLs, hashtags, etc)", 40 + "items": { "type": "ref", "ref": "sh.comet.v0.richtext.facet" } 41 + }, 42 + "createdAt": { "type": "string", "format": "datetime" } 43 + } 44 + } 45 + } 46 + } 47 + }
+38
lexicons/sh/comet/v0/feed/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.defs", 4 + "defs": { 5 + "link": { 6 + "type": "object", 7 + "description": "Link for the track. Usually to acquire it in some way, e.g. via free download or purchase. | TODO: multiple links?", 8 + "required": ["type", "value"], 9 + "properties": { 10 + "type": { 11 + "type": "string", 12 + "knownValues": [ 13 + "sh.comet.v0.feed.defs#downloadLink", 14 + "sh.comet.v0.feed.defs#buyLink" 15 + ] 16 + }, 17 + "value": { "type": "string", "format": "uri" } 18 + } 19 + }, 20 + "downloadLink": { 21 + "type": "token", 22 + "description": "Indicate the link leads to a free download for the track." 23 + }, 24 + "buyLink": { 25 + "type": "token", 26 + "description": "Indicate the link leads to a purchase page for the track." 27 + }, 28 + "viewerState": { 29 + "type": "object", 30 + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", 31 + "properties": { 32 + "like": { "type": "string", "format": "at-uri" }, 33 + "repost": { "type": "string", "format": "at-uri" }, 34 + "featured": { "type": "boolean" } 35 + } 36 + } 37 + } 38 + }
+44
lexicons/sh/comet/v0/feed/getActorPlaylists.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.getActorPlaylists", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a list of an actor's playlists.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "at-identifier" 15 + }, 16 + "limit": { 17 + "type": "integer", 18 + "minimum": 1, 19 + "maximum": 100, 20 + "default": 50 21 + }, 22 + "cursor": { "type": "string" } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "object", 29 + "required": ["playlists"], 30 + "properties": { 31 + "cursor": { "type": "string" }, 32 + "playlists": { 33 + "type": "array", 34 + "items": { 35 + "type": "ref", 36 + "ref": "sh.comet.v0.feed.playlist#view" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+44
lexicons/sh/comet/v0/feed/getActorTracks.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.getActorTracks", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a list of an actor's tracks.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "at-identifier" 15 + }, 16 + "limit": { 17 + "type": "integer", 18 + "minimum": 1, 19 + "maximum": 100, 20 + "default": 50 21 + }, 22 + "cursor": { "type": "string" } 23 + } 24 + }, 25 + "output": { 26 + "encoding": "application/json", 27 + "schema": { 28 + "type": "object", 29 + "required": ["tracks"], 30 + "properties": { 31 + "cursor": { "type": "string" }, 32 + "tracks": { 33 + "type": "array", 34 + "items": { 35 + "type": "ref", 36 + "ref": "sh.comet.v0.feed.track#view" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+19
lexicons/sh/comet/v0/feed/like.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.like", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record representing a 'like' of some media. Weakly linked with just an at-uri.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { "type": "string", "format": "at-uri" }, 14 + "createdAt": { "type": "string", "format": "datetime" } 15 + } 16 + } 17 + } 18 + } 19 + }
+19
lexicons/sh/comet/v0/feed/play.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.play", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record representing a 'play' of some media.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { "type": "string", "format": "at-uri" }, 14 + "createdAt": { "type": "string", "format": "datetime" } 15 + } 16 + } 17 + } 18 + } 19 + }
+114
lexicons/sh/comet/v0/feed/playlist.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.playlist", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A Comet playlist, containing many audio tracks.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["title", "type", "createdAt"], 12 + "properties": { 13 + "image": { 14 + "type": "blob", 15 + "description": "Image to be displayed representing the playlist.", 16 + "accept": ["image/png", "image/jpeg"], 17 + "maxSize": 1000000 18 + }, 19 + "title": { 20 + "type": "string", 21 + "description": "Title of the playlist.", 22 + "minLength": 1, 23 + "maxLength": 2560, 24 + "maxGraphemes": 256 25 + }, 26 + "description": { 27 + "type": "string", 28 + "description": "Description of the playlist.", 29 + "maxLength": 20000, 30 + "maxGraphemes": 2000 31 + }, 32 + "descriptionFacets": { 33 + "type": "ref", 34 + "description": "Annotations of the playlist's description.", 35 + "ref": "sh.comet.v0.richtext.facet" 36 + }, 37 + "type": { 38 + "type": "string", 39 + "description": "Type of the playlist. Allows differentiating between playlist's different purposes.", 40 + "knownValues": [ 41 + "sh.comet.v0.feed.playlist#album", 42 + "sh.comet.v0.feed.playlist#compilation", 43 + "sh.comet.v0.feed.playlist#playlist", 44 + "sh.comet.v0.feed.playlist#podcast" 45 + ] 46 + }, 47 + "tags": { 48 + "type": "array", 49 + "description": "Hashtags for the playlist, usually for genres.", 50 + "maxLength": 8, 51 + "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 52 + }, 53 + "link": { 54 + "type": "ref", 55 + "ref": "sh.comet.v0.feed.defs#link" 56 + }, 57 + "createdAt": { 58 + "type": "string", 59 + "format": "datetime", 60 + "description": "Timestamp for when the playlist was originally created." 61 + } 62 + } 63 + } 64 + }, 65 + "album": { 66 + "type": "token", 67 + "description": "Indicates the playlist is an album or extended play." 68 + }, 69 + "compilation": { 70 + "type": "token", 71 + "description": "Indicates the playlist is a compilation of various tracks, usually put together by a label." 72 + }, 73 + "playlist": { 74 + "type": "token", 75 + "description": "Indicates the playlist is a miscellaneous list of tracks." 76 + }, 77 + "podcast": { 78 + "type": "token", 79 + "description": "Indicates the playlist is a podcast or radio show split into several individual tracks." 80 + }, 81 + "view": { 82 + "type": "object", 83 + "required": ["uri", "cid", "author", "tracks", "record"], 84 + "properties": { 85 + "uri": { "type": "string", "format": "at-uri" }, 86 + "cid": { "type": "string", "format": "cid" }, 87 + "author": { 88 + "type": "ref", 89 + "ref": "sh.comet.v0.actor.profile#viewFull" 90 + }, 91 + "tracks": { 92 + "type": "array", 93 + "items": { "type": "ref", "ref": "sh.comet.v0.feed.track#view" }, 94 + "description": "TODO: include cursor for pagination if too many?" 95 + }, 96 + "image": { 97 + "type": "string", 98 + "format": "uri", 99 + "description": "URL pointing to where the image for the playlist can be fetched." 100 + }, 101 + "record": { 102 + "type": "ref", 103 + "ref": "#main" 104 + }, 105 + "repostCount": { "type": "integer" }, 106 + "likeCount": { "type": "integer" }, 107 + "commentCount": { "type": "integer" }, 108 + "trackCount": { "type": "integer" }, 109 + "indexedAt": { "type": "string", "format": "datetime" }, 110 + "viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" } 111 + } 112 + } 113 + } 114 + }
+31
lexicons/sh/comet/v0/feed/playlistTrack.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.playlistTrack", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A link between a Comet track and a playlist.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["track", "playlist", "position", "createdAt"], 12 + "properties": { 13 + "track": { 14 + "type": "string", 15 + "format": "at-uri" 16 + }, 17 + "playlist": { 18 + "type": "string", 19 + "format": "at-uri" 20 + }, 21 + "position": { "type": "integer", "minimum": 0 }, 22 + "createdAt": { 23 + "type": "string", 24 + "format": "datetime", 25 + "description": "Timestamp for when the track entry was originally added to the playlist." 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+19
lexicons/sh/comet/v0/feed/repost.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.repost", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record representing a 'repost' of some media. Weakly linked with just an at-uri.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["subject", "createdAt"], 12 + "properties": { 13 + "subject": { "type": "string", "format": "at-uri" }, 14 + "createdAt": { "type": "string", "format": "datetime" } 15 + } 16 + } 17 + } 18 + } 19 + }
+103
lexicons/sh/comet/v0/feed/track.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.feed.track", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A Comet audio track. TODO: should probably have some sort of pre-calculated waveform, or have a query to get one from a blob?", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["audio", "title", "createdAt"], 12 + "properties": { 13 + "audio": { 14 + "type": "blob", 15 + "description": "Audio of the track, ideally encoded as 96k Opus. Limited to 100mb.", 16 + "accept": ["audio/ogg"], 17 + "maxSize": 100000000 18 + }, 19 + "image": { 20 + "type": "blob", 21 + "description": "Image to be displayed representing the track.", 22 + "accept": ["image/png", "image/jpeg"], 23 + "maxSize": 1000000 24 + }, 25 + "title": { 26 + "type": "string", 27 + "description": "Title of the track. Usually shouldn't include the creator's name.", 28 + "minLength": 1, 29 + "maxLength": 2560, 30 + "maxGraphemes": 256 31 + }, 32 + "description": { 33 + "type": "string", 34 + "description": "Description of the track.", 35 + "maxLength": 20000, 36 + "maxGraphemes": 2000 37 + }, 38 + "descriptionFacets": { 39 + "type": "ref", 40 + "description": "Annotations of the track's description.", 41 + "ref": "sh.comet.v0.richtext.facet" 42 + }, 43 + "explicit": { 44 + "type": "boolean", 45 + "description": "Whether the track contains explicit content that may objectionable to some people, usually swearing or adult themes." 46 + }, 47 + "tags": { 48 + "type": "array", 49 + "description": "Hashtags for the track, usually for genres.", 50 + "maxLength": 8, 51 + "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 52 + }, 53 + "link": { 54 + "type": "ref", 55 + "ref": "sh.comet.v0.feed.defs#link" 56 + }, 57 + "createdAt": { 58 + "type": "string", 59 + "format": "datetime", 60 + "description": "Timestamp for when the track entry was originally created." 61 + }, 62 + "releasedAt": { 63 + "type": "string", 64 + "format": "datetime", 65 + "description": "Timestamp for when the track was released. If in the future, may be used to implement pre-savable tracks." 66 + } 67 + } 68 + } 69 + }, 70 + "view": { 71 + "type": "object", 72 + "required": ["uri", "cid", "author", "audio", "record", "indexedAt"], 73 + "properties": { 74 + "uri": { "type": "string", "format": "at-uri" }, 75 + "cid": { "type": "string", "format": "cid" }, 76 + "author": { 77 + "type": "ref", 78 + "ref": "sh.comet.v0.actor.profile#viewFull" 79 + }, 80 + "audio": { 81 + "type": "string", 82 + "format": "uri", 83 + "description": "URL pointing to where the audio data for the track can be fetched. May be re-encoded from the original blob." 84 + }, 85 + "image": { 86 + "type": "string", 87 + "format": "uri", 88 + "description": "URL pointing to where the image for the track can be fetched." 89 + }, 90 + "record": { 91 + "type": "ref", 92 + "ref": "#main" 93 + }, 94 + "repostCount": { "type": "integer" }, 95 + "likeCount": { "type": "integer" }, 96 + "playCount": { "type": "integer" }, 97 + "commentCount": { "type": "integer" }, 98 + "indexedAt": { "type": "string", "format": "datetime" }, 99 + "viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" } 100 + } 101 + } 102 + } 103 + }
+62
lexicons/sh/comet/v0/richtext/facet.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.comet.v0.richtext.facet", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "description": "Annotation of a sub-string within rich text.", 8 + "required": ["index", "features"], 9 + "properties": { 10 + "index": { "type": "ref", "ref": "#byteSlice" }, 11 + "features": { 12 + "type": "array", 13 + "items": { "type": "union", "refs": ["#mention", "#link", "#tag"] } 14 + } 15 + } 16 + }, 17 + "mention": { 18 + "type": "object", 19 + "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", 20 + "required": ["did"], 21 + "properties": { 22 + "did": { "type": "string", "format": "did" } 23 + } 24 + }, 25 + "link": { 26 + "type": "object", 27 + "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.", 28 + "required": ["uri"], 29 + "properties": { 30 + "uri": { "type": "string", "format": "uri" } 31 + } 32 + }, 33 + "tag": { 34 + "type": "object", 35 + "description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", 36 + "required": ["tag"], 37 + "properties": { 38 + "tag": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 39 + } 40 + }, 41 + "timestamp": { 42 + "type": "object", 43 + "description": "Facet feature for a timestamp in a track. The text usually is in the format of 'hh:mm:ss' with the hour section being omitted if unnecessary.", 44 + "properties": { 45 + "timestamp": { 46 + "type": "integer", 47 + "minimum": 0, 48 + "description": "Reference time, in seconds." 49 + } 50 + } 51 + }, 52 + "byteSlice": { 53 + "type": "object", 54 + "description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.", 55 + "required": ["byteStart", "byteEnd"], 56 + "properties": { 57 + "byteStart": { "type": "integer", "minimum": 0 }, 58 + "byteEnd": { "type": "integer", "minimum": 0 } 59 + } 60 + } 61 + } 62 + }
+34
lib/comet/application.ex
··· 1 + defmodule Comet.Application do 2 + # See https://hexdocs.pm/elixir/Application.html 3 + # for more information on OTP Applications 4 + @moduledoc false 5 + 6 + use Application 7 + 8 + @impl true 9 + def start(_type, _args) do 10 + children = [ 11 + CometWeb.Telemetry, 12 + Comet.Repo, 13 + {DNSCluster, query: Application.get_env(:comet, :dns_cluster_query) || :ignore}, 14 + {Phoenix.PubSub, name: Comet.PubSub}, 15 + # Start a worker by calling: Comet.Worker.start_link(arg) 16 + # {Comet.Worker, arg}, 17 + # Start to serve requests, typically the last entry 18 + CometWeb.Endpoint 19 + ] 20 + 21 + # See https://hexdocs.pm/elixir/Supervisor.html 22 + # for other strategies and supported options 23 + opts = [strategy: :one_for_one, name: Comet.Supervisor] 24 + Supervisor.start_link(children, opts) 25 + end 26 + 27 + # Tell Phoenix to update the endpoint configuration 28 + # whenever the application is updated. 29 + @impl true 30 + def config_change(changed, _new, removed) do 31 + CometWeb.Endpoint.config_change(changed, removed) 32 + :ok 33 + end 34 + end
+3
lib/comet/mailer.ex
··· 1 + defmodule Comet.Mailer do 2 + use Swoosh.Mailer, otp_app: :comet 3 + end
+5
lib/comet/repo.ex
··· 1 + defmodule Comet.Repo do 2 + use Ecto.Repo, 3 + otp_app: :comet, 4 + adapter: Ecto.Adapters.Postgres 5 + end
+9
lib/comet.ex
··· 1 + defmodule Comet do 2 + @moduledoc """ 3 + Comet keeps the contexts that define your domain 4 + and business logic. 5 + 6 + Contexts are also responsible for managing your data, regardless 7 + if it comes from the database, an external API or others. 8 + """ 9 + end
+11
lib/comet_app/components/title.ex
··· 1 + defmodule CometApp.Components.Title do 2 + use Hologram.Component 3 + 4 + prop :text, :string, default: nil 5 + 6 + def template do 7 + ~HOLO""" 8 + <title>{if @text do "#{@text} - Comet" else "Comet" end}</title> 9 + """ 10 + end 11 + end
+27
lib/comet_app/layouts/main.ex
··· 1 + defmodule CometApp.MainLayout do 2 + use Hologram.Component 3 + 4 + alias CometApp.Components.Title 5 + alias Hologram.UI.Runtime 6 + 7 + prop :page_title, :string, default: nil 8 + 9 + def template do 10 + ~HOLO""" 11 + <!DOCTYPE html> 12 + <html lang="en"> 13 + <head> 14 + <meta charset="utf-8" /> 15 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 16 + <link rel="stylesheet" href="/assets/js/app.css" /> 17 + <link rel="stylesheet" href="/assets/css/app.css" /> 18 + <Title text={@page_title} /> 19 + <Runtime /> 20 + </head> 21 + <body class="from-amber-900 bg-linear-to-tl to-stone-950 to-80% bg-fixed text-white p-10 w-screen h-screen flex justify-center flex-col"> 22 + <slot/> 23 + </body> 24 + </html> 25 + """ 26 + end 27 + end
+28
lib/comet_app/pages/home.ex
··· 1 + defmodule CometApp.HomePage do 2 + use Hologram.Page 3 + 4 + route "/" 5 + 6 + layout CometApp.MainLayout, page_title: "Home" 7 + 8 + # def init(_params, component, _server) do 9 + # # In real app, fetch from database 10 + # posts = [ 11 + # %{id: 1, title: "First Post", excerpt: "This is my first post"}, 12 + # %{id: 2, title: "Second Post", excerpt: nil} 13 + # ] 14 + 15 + # put_state(component, :posts, posts) 16 + # end 17 + 18 + def template do 19 + ~HOLO""" 20 + <h1 class="italic text-9xl tracking-tighter leading-30 mb-10 font-thin"> 21 + <span class="font-medium">Comet</span> is the next-generation of music streaming. 22 + </h1> 23 + <p class="text-2xl font-light"> 24 + Powered by the AT Protocol, we're putting the power back in the hands of musicians. 25 + </p> 26 + """ 27 + end 28 + end
+493
lib/comet_web/components/core_components.ex
··· 1 + defmodule CometWeb.CoreComponents do 2 + @moduledoc """ 3 + Provides core UI components. 4 + 5 + At first glance, this module may seem daunting, but its goal is to provide 6 + core building blocks for your application, such as tables, forms, and 7 + inputs. The components consist mostly of markup and are well-documented 8 + with doc strings and declarative assigns. You may customize and style 9 + them in any way you want, based on your application growth and needs. 10 + 11 + The foundation for styling is Tailwind CSS, a utility-first CSS framework. Here are useful references: 12 + 13 + * [Tailwind CSS](https://tailwindcss.com) - the foundational framework 14 + we build on. You will use it for layout, sizing, flexbox, grid, and 15 + spacing. 16 + 17 + * [Heroicons](https://heroicons.com) - see `icon/1` for usage. 18 + 19 + * [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) - 20 + the component system used by Phoenix. Some components, such as `<.link>` 21 + and `<.form>`, are defined there. 22 + 23 + """ 24 + use Phoenix.Component 25 + use Gettext, backend: CometWeb.Gettext 26 + 27 + alias Phoenix.LiveView.JS 28 + 29 + @doc """ 30 + Renders flash notices. 31 + 32 + ## Examples 33 + 34 + <.flash kind={:info} flash={@flash} /> 35 + <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!</.flash> 36 + """ 37 + attr :id, :string, doc: "the optional id of flash container" 38 + attr :flash, :map, default: %{}, doc: "the map of flash messages to display" 39 + attr :title, :string, default: nil 40 + attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" 41 + attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container" 42 + 43 + slot :inner_block, doc: "the optional inner block that renders the flash message" 44 + 45 + def flash(assigns) do 46 + assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end) 47 + 48 + ~H""" 49 + <div 50 + :if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)} 51 + id={@id} 52 + phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")} 53 + role="alert" 54 + class="toast toast-top toast-end z-50" 55 + {@rest} 56 + > 57 + <div class={[ 58 + "alert w-80 sm:w-96 max-w-80 sm:max-w-96 text-wrap", 59 + @kind == :info && "alert-info", 60 + @kind == :error && "alert-error" 61 + ]}> 62 + <.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" /> 63 + <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" /> 64 + <div> 65 + <p :if={@title} class="font-semibold">{@title}</p> 66 + <p>{msg}</p> 67 + </div> 68 + <div class="flex-1" /> 69 + <button type="button" class="group self-start cursor-pointer" aria-label={gettext("close")}> 70 + <.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" /> 71 + </button> 72 + </div> 73 + </div> 74 + """ 75 + end 76 + 77 + @doc """ 78 + Renders a button with navigation support. 79 + 80 + ## Examples 81 + 82 + <.button>Send!</.button> 83 + <.button phx-click="go" variant="primary">Send!</.button> 84 + <.button navigate={~p"/"}>Home</.button> 85 + """ 86 + attr :rest, :global, include: ~w(href navigate patch method download name value disabled) 87 + attr :class, :any 88 + attr :variant, :string, values: ~w(primary) 89 + slot :inner_block, required: true 90 + 91 + def button(%{rest: rest} = assigns) do 92 + variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"} 93 + 94 + assigns = 95 + assign_new(assigns, :class, fn -> 96 + ["btn", Map.fetch!(variants, assigns[:variant])] 97 + end) 98 + 99 + if rest[:href] || rest[:navigate] || rest[:patch] do 100 + ~H""" 101 + <.link class={@class} {@rest}> 102 + {render_slot(@inner_block)} 103 + </.link> 104 + """ 105 + else 106 + ~H""" 107 + <button class={@class} {@rest}> 108 + {render_slot(@inner_block)} 109 + </button> 110 + """ 111 + end 112 + end 113 + 114 + @doc """ 115 + Renders an input with label and error messages. 116 + 117 + A `Phoenix.HTML.FormField` may be passed as argument, 118 + which is used to retrieve the input name, id, and values. 119 + Otherwise all attributes may be passed explicitly. 120 + 121 + ## Types 122 + 123 + This function accepts all HTML input types, considering that: 124 + 125 + * You may also set `type="select"` to render a `<select>` tag 126 + 127 + * `type="checkbox"` is used exclusively to render boolean values 128 + 129 + * For live file uploads, see `Phoenix.Component.live_file_input/1` 130 + 131 + See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input 132 + for more information. Unsupported types, such as radio, are best 133 + written directly in your templates. 134 + 135 + ## Examples 136 + 137 + ```heex 138 + <.input field={@form[:email]} type="email" /> 139 + <.input name="my-input" errors={["oh no!"]} /> 140 + ``` 141 + 142 + ## Select type 143 + 144 + When using `type="select"`, you must pass the `options` and optionally 145 + a `value` to mark which option should be preselected. 146 + 147 + ```heex 148 + <.input field={@form[:user_type]} type="select" options={["Admin": "admin", "User": "user"]} /> 149 + ``` 150 + 151 + For more information on what kind of data can be passed to `options` see 152 + [`options_for_select`](https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#options_for_select/2). 153 + """ 154 + attr :id, :any, default: nil 155 + attr :name, :any 156 + attr :label, :string, default: nil 157 + attr :value, :any 158 + 159 + attr :type, :string, 160 + default: "text", 161 + values: ~w(checkbox color date datetime-local email file month number password 162 + search select tel text textarea time url week hidden) 163 + 164 + attr :field, Phoenix.HTML.FormField, 165 + doc: "a form field struct retrieved from the form, for example: @form[:email]" 166 + 167 + attr :errors, :list, default: [] 168 + attr :checked, :boolean, doc: "the checked flag for checkbox inputs" 169 + attr :prompt, :string, default: nil, doc: "the prompt for select inputs" 170 + attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2" 171 + attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs" 172 + attr :class, :any, default: nil, doc: "the input class to use over defaults" 173 + attr :error_class, :any, default: nil, doc: "the input error class to use over defaults" 174 + 175 + attr :rest, :global, 176 + include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength 177 + multiple pattern placeholder readonly required rows size step) 178 + 179 + def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do 180 + errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] 181 + 182 + assigns 183 + |> assign(field: nil, id: assigns.id || field.id) 184 + |> assign(:errors, Enum.map(errors, &translate_error(&1))) 185 + |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end) 186 + |> assign_new(:value, fn -> field.value end) 187 + |> input() 188 + end 189 + 190 + def input(%{type: "hidden"} = assigns) do 191 + ~H""" 192 + <input type="hidden" id={@id} name={@name} value={@value} {@rest} /> 193 + """ 194 + end 195 + 196 + def input(%{type: "checkbox"} = assigns) do 197 + assigns = 198 + assign_new(assigns, :checked, fn -> 199 + Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value]) 200 + end) 201 + 202 + ~H""" 203 + <div class="fieldset mb-2"> 204 + <label> 205 + <input 206 + type="hidden" 207 + name={@name} 208 + value="false" 209 + disabled={@rest[:disabled]} 210 + form={@rest[:form]} 211 + /> 212 + <span class="label"> 213 + <input 214 + type="checkbox" 215 + id={@id} 216 + name={@name} 217 + value="true" 218 + checked={@checked} 219 + class={@class || "checkbox checkbox-sm"} 220 + {@rest} 221 + />{@label} 222 + </span> 223 + </label> 224 + <.error :for={msg <- @errors}>{msg}</.error> 225 + </div> 226 + """ 227 + end 228 + 229 + def input(%{type: "select"} = assigns) do 230 + ~H""" 231 + <div class="fieldset mb-2"> 232 + <label> 233 + <span :if={@label} class="label mb-1">{@label}</span> 234 + <select 235 + id={@id} 236 + name={@name} 237 + class={[@class || "w-full select", @errors != [] && (@error_class || "select-error")]} 238 + multiple={@multiple} 239 + {@rest} 240 + > 241 + <option :if={@prompt} value="">{@prompt}</option> 242 + {Phoenix.HTML.Form.options_for_select(@options, @value)} 243 + </select> 244 + </label> 245 + <.error :for={msg <- @errors}>{msg}</.error> 246 + </div> 247 + """ 248 + end 249 + 250 + def input(%{type: "textarea"} = assigns) do 251 + ~H""" 252 + <div class="fieldset mb-2"> 253 + <label> 254 + <span :if={@label} class="label mb-1">{@label}</span> 255 + <textarea 256 + id={@id} 257 + name={@name} 258 + class={[ 259 + @class || "w-full textarea", 260 + @errors != [] && (@error_class || "textarea-error") 261 + ]} 262 + {@rest} 263 + >{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea> 264 + </label> 265 + <.error :for={msg <- @errors}>{msg}</.error> 266 + </div> 267 + """ 268 + end 269 + 270 + # All other inputs text, datetime-local, url, password, etc. are handled here... 271 + def input(assigns) do 272 + ~H""" 273 + <div class="fieldset mb-2"> 274 + <label> 275 + <span :if={@label} class="label mb-1">{@label}</span> 276 + <input 277 + type={@type} 278 + name={@name} 279 + id={@id} 280 + value={Phoenix.HTML.Form.normalize_value(@type, @value)} 281 + class={[ 282 + @class || "w-full input", 283 + @errors != [] && (@error_class || "input-error") 284 + ]} 285 + {@rest} 286 + /> 287 + </label> 288 + <.error :for={msg <- @errors}>{msg}</.error> 289 + </div> 290 + """ 291 + end 292 + 293 + # Helper used by inputs to generate form errors 294 + defp error(assigns) do 295 + ~H""" 296 + <p class="mt-1.5 flex gap-2 items-center text-sm text-error"> 297 + <.icon name="hero-exclamation-circle" class="size-5" /> 298 + {render_slot(@inner_block)} 299 + </p> 300 + """ 301 + end 302 + 303 + @doc """ 304 + Renders a header with title. 305 + """ 306 + slot :inner_block, required: true 307 + slot :subtitle 308 + slot :actions 309 + 310 + def header(assigns) do 311 + ~H""" 312 + <header class={[@actions != [] && "flex items-center justify-between gap-6", "pb-4"]}> 313 + <div> 314 + <h1 class="text-lg font-semibold leading-8"> 315 + {render_slot(@inner_block)} 316 + </h1> 317 + <p :if={@subtitle != []} class="text-sm text-base-content/70"> 318 + {render_slot(@subtitle)} 319 + </p> 320 + </div> 321 + <div class="flex-none">{render_slot(@actions)}</div> 322 + </header> 323 + """ 324 + end 325 + 326 + @doc """ 327 + Renders a table with generic styling. 328 + 329 + ## Examples 330 + 331 + <.table id="users" rows={@users}> 332 + <:col :let={user} label="id">{user.id}</:col> 333 + <:col :let={user} label="username">{user.username}</:col> 334 + </.table> 335 + """ 336 + attr :id, :string, required: true 337 + attr :rows, :list, required: true 338 + attr :row_id, :any, default: nil, doc: "the function for generating the row id" 339 + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" 340 + 341 + attr :row_item, :any, 342 + default: &Function.identity/1, 343 + doc: "the function for mapping each row before calling the :col and :action slots" 344 + 345 + slot :col, required: true do 346 + attr :label, :string 347 + end 348 + 349 + slot :action, doc: "the slot for showing user actions in the last table column" 350 + 351 + def table(assigns) do 352 + assigns = 353 + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do 354 + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) 355 + end 356 + 357 + ~H""" 358 + <table class="table table-zebra"> 359 + <thead> 360 + <tr> 361 + <th :for={col <- @col}>{col[:label]}</th> 362 + <th :if={@action != []}> 363 + <span class="sr-only">{gettext("Actions")}</span> 364 + </th> 365 + </tr> 366 + </thead> 367 + <tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}> 368 + <tr :for={row <- @rows} id={@row_id && @row_id.(row)}> 369 + <td 370 + :for={col <- @col} 371 + phx-click={@row_click && @row_click.(row)} 372 + class={@row_click && "hover:cursor-pointer"} 373 + > 374 + {render_slot(col, @row_item.(row))} 375 + </td> 376 + <td :if={@action != []} class="w-0 font-semibold"> 377 + <div class="flex gap-4"> 378 + <%= for action <- @action do %> 379 + {render_slot(action, @row_item.(row))} 380 + <% end %> 381 + </div> 382 + </td> 383 + </tr> 384 + </tbody> 385 + </table> 386 + """ 387 + end 388 + 389 + @doc """ 390 + Renders a data list. 391 + 392 + ## Examples 393 + 394 + <.list> 395 + <:item title="Title">{@post.title}</:item> 396 + <:item title="Views">{@post.views}</:item> 397 + </.list> 398 + """ 399 + slot :item, required: true do 400 + attr :title, :string, required: true 401 + end 402 + 403 + def list(assigns) do 404 + ~H""" 405 + <ul class="list"> 406 + <li :for={item <- @item} class="list-row"> 407 + <div class="list-col-grow"> 408 + <div class="font-bold">{item.title}</div> 409 + <div>{render_slot(item)}</div> 410 + </div> 411 + </li> 412 + </ul> 413 + """ 414 + end 415 + 416 + @doc """ 417 + Renders a [Heroicon](https://heroicons.com). 418 + 419 + Heroicons come in three styles โ€“ outline, solid, and mini. 420 + By default, the outline style is used, but solid and mini may 421 + be applied by using the `-solid` and `-mini` suffix. 422 + 423 + You can customize the size and colors of the icons by setting 424 + width, height, and background color classes. 425 + 426 + Icons are extracted from the `deps/heroicons` directory and bundled within 427 + your compiled app.css by the plugin in `assets/vendor/heroicons.js`. 428 + 429 + ## Examples 430 + 431 + <.icon name="hero-x-mark" /> 432 + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> 433 + """ 434 + attr :name, :string, required: true 435 + attr :class, :any, default: "size-4" 436 + 437 + def icon(%{name: "hero-" <> _} = assigns) do 438 + ~H""" 439 + <span class={[@name, @class]} /> 440 + """ 441 + end 442 + 443 + ## JS Commands 444 + 445 + def show(js \\ %JS{}, selector) do 446 + JS.show(js, 447 + to: selector, 448 + time: 300, 449 + transition: 450 + {"transition-all ease-out duration-300", 451 + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", 452 + "opacity-100 translate-y-0 sm:scale-100"} 453 + ) 454 + end 455 + 456 + def hide(js \\ %JS{}, selector) do 457 + JS.hide(js, 458 + to: selector, 459 + time: 200, 460 + transition: 461 + {"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100", 462 + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} 463 + ) 464 + end 465 + 466 + @doc """ 467 + Translates an error message using gettext. 468 + """ 469 + def translate_error({msg, opts}) do 470 + # When using gettext, we typically pass the strings we want 471 + # to translate as a static argument: 472 + # 473 + # # Translate the number of files with plural rules 474 + # dngettext("errors", "1 file", "%{count} files", count) 475 + # 476 + # However the error messages in our forms and APIs are generated 477 + # dynamically, so we need to translate them by calling Gettext 478 + # with our gettext backend as first argument. Translations are 479 + # available in the errors.po file (as we use the "errors" domain). 480 + if count = opts[:count] do 481 + Gettext.dngettext(CometWeb.Gettext, "errors", msg, msg, count, opts) 482 + else 483 + Gettext.dgettext(CometWeb.Gettext, "errors", msg, opts) 484 + end 485 + end 486 + 487 + @doc """ 488 + Translates the errors for a field from a keyword list of errors. 489 + """ 490 + def translate_errors(errors, field) when is_list(errors) do 491 + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) 492 + end 493 + end
+37
lib/comet_web/components/layouts/root.html.heex
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 + <meta name="csrf-token" content={get_csrf_token()} /> 7 + <.live_title default="Comet" suffix=" ยท Phoenix Framework"> 8 + {assigns[:page_title]} 9 + </.live_title> 10 + <link phx-track-static rel="stylesheet" href={~p"/assets/js/app.css"} /> 11 + <link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} /> 12 + <script defer phx-track-static type="text/javascript" src={~p"/assets/js/app.js"}> 13 + </script> 14 + <script> 15 + (() => { 16 + const setTheme = (theme) => { 17 + if (theme === "system") { 18 + localStorage.removeItem("phx:theme"); 19 + document.documentElement.removeAttribute("data-theme"); 20 + } else { 21 + localStorage.setItem("phx:theme", theme); 22 + document.documentElement.setAttribute("data-theme", theme); 23 + } 24 + }; 25 + if (!document.documentElement.hasAttribute("data-theme")) { 26 + setTheme(localStorage.getItem("phx:theme") || "system"); 27 + } 28 + window.addEventListener("storage", (e) => e.key === "phx:theme" && setTheme(e.newValue || "system")); 29 + 30 + window.addEventListener("phx:set-theme", (e) => setTheme(e.target.dataset.phxTheme)); 31 + })(); 32 + </script> 33 + </head> 34 + <body> 35 + {@inner_content} 36 + </body> 37 + </html>
+154
lib/comet_web/components/layouts.ex
··· 1 + defmodule CometWeb.Layouts do 2 + @moduledoc """ 3 + This module holds layouts and related functionality 4 + used by your application. 5 + """ 6 + use CometWeb, :html 7 + 8 + # Embed all files in layouts/* within this module. 9 + # The default root.html.heex file contains the HTML 10 + # skeleton of your application, namely HTML headers 11 + # and other static content. 12 + embed_templates "layouts/*" 13 + 14 + @doc """ 15 + Renders your app layout. 16 + 17 + This function is typically invoked from every template, 18 + and it often contains your application menu, sidebar, 19 + or similar. 20 + 21 + ## Examples 22 + 23 + <Layouts.app flash={@flash}> 24 + <h1>Content</h1> 25 + </Layouts.app> 26 + 27 + """ 28 + attr :flash, :map, required: true, doc: "the map of flash messages" 29 + 30 + attr :current_scope, :map, 31 + default: nil, 32 + doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)" 33 + 34 + slot :inner_block, required: true 35 + 36 + def app(assigns) do 37 + ~H""" 38 + <header class="navbar px-4 sm:px-6 lg:px-8"> 39 + <div class="flex-1"> 40 + <a href="/" class="flex-1 flex w-fit items-center gap-2"> 41 + <img src={~p"/images/logo.svg"} width="36" /> 42 + <span class="text-sm font-semibold">v{Application.spec(:phoenix, :vsn)}</span> 43 + </a> 44 + </div> 45 + <div class="flex-none"> 46 + <ul class="flex flex-column px-1 space-x-4 items-center"> 47 + <li> 48 + <a href="https://phoenixframework.org/" class="btn btn-ghost">Website</a> 49 + </li> 50 + <li> 51 + <a href="https://github.com/phoenixframework/phoenix" class="btn btn-ghost">GitHub</a> 52 + </li> 53 + <li> 54 + <.theme_toggle /> 55 + </li> 56 + <li> 57 + <a href="https://hexdocs.pm/phoenix/overview.html" class="btn btn-primary"> 58 + Get Started <span aria-hidden="true">&rarr;</span> 59 + </a> 60 + </li> 61 + </ul> 62 + </div> 63 + </header> 64 + 65 + <main class="px-4 py-20 sm:px-6 lg:px-8"> 66 + <div class="mx-auto max-w-2xl space-y-4"> 67 + {render_slot(@inner_block)} 68 + </div> 69 + </main> 70 + 71 + <.flash_group flash={@flash} /> 72 + """ 73 + end 74 + 75 + @doc """ 76 + Shows the flash group with standard titles and content. 77 + 78 + ## Examples 79 + 80 + <.flash_group flash={@flash} /> 81 + """ 82 + attr :flash, :map, required: true, doc: "the map of flash messages" 83 + attr :id, :string, default: "flash-group", doc: "the optional id of flash container" 84 + 85 + def flash_group(assigns) do 86 + ~H""" 87 + <div id={@id} aria-live="polite"> 88 + <.flash kind={:info} flash={@flash} /> 89 + <.flash kind={:error} flash={@flash} /> 90 + 91 + <.flash 92 + id="client-error" 93 + kind={:error} 94 + title={gettext("We can't find the internet")} 95 + phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")} 96 + phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})} 97 + hidden 98 + > 99 + {gettext("Attempting to reconnect")} 100 + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> 101 + </.flash> 102 + 103 + <.flash 104 + id="server-error" 105 + kind={:error} 106 + title={gettext("Something went wrong!")} 107 + phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")} 108 + phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})} 109 + hidden 110 + > 111 + {gettext("Attempting to reconnect")} 112 + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> 113 + </.flash> 114 + </div> 115 + """ 116 + end 117 + 118 + @doc """ 119 + Provides dark vs light theme toggle based on themes defined in app.css. 120 + 121 + See <head> in root.html.heex which applies the theme before page load. 122 + """ 123 + def theme_toggle(assigns) do 124 + ~H""" 125 + <div class="card relative flex flex-row items-center border-2 border-base-300 bg-base-300 rounded-full"> 126 + <div class="absolute w-1/3 h-full rounded-full border-1 border-base-200 bg-base-100 brightness-200 left-0 [[data-theme=light]_&]:left-1/3 [[data-theme=dark]_&]:left-2/3 transition-[left]" /> 127 + 128 + <button 129 + class="flex p-2 cursor-pointer w-1/3" 130 + phx-click={JS.dispatch("phx:set-theme")} 131 + data-phx-theme="system" 132 + > 133 + <.icon name="hero-computer-desktop-micro" class="size-4 opacity-75 hover:opacity-100" /> 134 + </button> 135 + 136 + <button 137 + class="flex p-2 cursor-pointer w-1/3" 138 + phx-click={JS.dispatch("phx:set-theme")} 139 + data-phx-theme="light" 140 + > 141 + <.icon name="hero-sun-micro" class="size-4 opacity-75 hover:opacity-100" /> 142 + </button> 143 + 144 + <button 145 + class="flex p-2 cursor-pointer w-1/3" 146 + phx-click={JS.dispatch("phx:set-theme")} 147 + data-phx-theme="dark" 148 + > 149 + <.icon name="hero-moon-micro" class="size-4 opacity-75 hover:opacity-100" /> 150 + </button> 151 + </div> 152 + """ 153 + end 154 + end
+24
lib/comet_web/controllers/error_html.ex
··· 1 + defmodule CometWeb.ErrorHTML do 2 + @moduledoc """ 3 + This module is invoked by your endpoint in case of errors on HTML requests. 4 + 5 + See config/config.exs. 6 + """ 7 + use CometWeb, :html 8 + 9 + # If you want to customize your error pages, 10 + # uncomment the embed_templates/1 call below 11 + # and add pages to the error directory: 12 + # 13 + # * lib/comet_web/controllers/error_html/404.html.heex 14 + # * lib/comet_web/controllers/error_html/500.html.heex 15 + # 16 + # embed_templates "error_html/*" 17 + 18 + # The default is to render a plain text page based on 19 + # the template name. For example, "404.html" becomes 20 + # "Not Found". 21 + def render(template, _assigns) do 22 + Phoenix.Controller.status_message_from_template(template) 23 + end 24 + end
+21
lib/comet_web/controllers/error_json.ex
··· 1 + defmodule CometWeb.ErrorJSON do 2 + @moduledoc """ 3 + This module is invoked by your endpoint in case of errors on JSON requests. 4 + 5 + See config/config.exs. 6 + """ 7 + 8 + # If you want to customize a particular status code, 9 + # you may add your own clauses, such as: 10 + # 11 + # def render("500.json", _assigns) do 12 + # %{errors: %{detail: "Internal Server Error"}} 13 + # end 14 + 15 + # By default, Phoenix returns the status message from 16 + # the template name. For example, "404.json" becomes 17 + # "Not Found". 18 + def render(template, _assigns) do 19 + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 20 + end 21 + end
+9
lib/comet_web/controllers/page_controller.ex
··· 1 + defmodule CometWeb.PageController do 2 + use CometWeb, :controller 3 + 4 + def home(conn, _params) do 5 + conn 6 + |> put_flash(:error, "What's up gang?") 7 + |> render(:home) 8 + end 9 + end
+202
lib/comet_web/controllers/page_html/home.html.heex
··· 1 + <Layouts.flash_group flash={@flash} /> 2 + <div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]"> 3 + <svg 4 + viewBox="0 0 1480 957" 5 + fill="none" 6 + aria-hidden="true" 7 + class="absolute inset-0 h-full w-full" 8 + preserveAspectRatio="xMinYMid slice" 9 + > 10 + <path fill="#EE7868" d="M0 0h1480v957H0z" /> 11 + <path 12 + d="M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z" 13 + fill="#FF9F92" 14 + /> 15 + <path 16 + d="M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z" 17 + fill="#FA8372" 18 + /> 19 + <path 20 + d="M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z" 21 + fill="#E96856" 22 + fill-opacity=".6" 23 + /> 24 + <path 25 + d="M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z" 26 + fill="#C42652" 27 + fill-opacity=".2" 28 + /> 29 + <path 30 + d="M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z" 31 + fill="#A41C42" 32 + fill-opacity=".2" 33 + /> 34 + <path 35 + d="M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z" 36 + fill="#A41C42" 37 + fill-opacity=".2" 38 + /> 39 + </svg> 40 + </div> 41 + <div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32"> 42 + <div class="mx-auto max-w-xl lg:mx-0"> 43 + <svg viewBox="0 0 71 48" class="h-12" aria-hidden="true"> 44 + <path 45 + d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z" 46 + fill="#FD4F00" 47 + /> 48 + </svg> 49 + <div class="mt-10 flex justify-between items-center"> 50 + <h1 class="flex items-center text-sm font-semibold leading-6"> 51 + Phoenix Framework 52 + <small class="badge badge-warning badge-sm ml-3"> 53 + v{Application.spec(:phoenix, :vsn)} 54 + </small> 55 + </h1> 56 + <Layouts.theme_toggle /> 57 + </div> 58 + 59 + <p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-balance"> 60 + Peace of mind from prototype to production. 61 + </p> 62 + <p class="mt-4 leading-7 text-base-content/70"> 63 + Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale. 64 + </p> 65 + <div class="flex"> 66 + <div class="w-full sm:w-auto"> 67 + <div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3"> 68 + <a 69 + href="https://hexdocs.pm/phoenix/overview.html" 70 + class="group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6" 71 + > 72 + <span class="absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105"> 73 + </span> 74 + <span class="relative flex items-center gap-4 sm:flex-col"> 75 + <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6"> 76 + <path d="m12 4 10-2v18l-10 2V4Z" fill="currentColor" fill-opacity=".15" /> 77 + <path 78 + d="M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2" 79 + stroke="currentColor" 80 + stroke-width="2" 81 + stroke-linecap="round" 82 + stroke-linejoin="round" 83 + /> 84 + </svg> 85 + Guides &amp; Docs 86 + </span> 87 + </a> 88 + <a 89 + href="https://github.com/phoenixframework/phoenix" 90 + class="group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6" 91 + > 92 + <span class="absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105"> 93 + </span> 94 + <span class="relative flex items-center gap-4 sm:flex-col"> 95 + <svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6"> 96 + <path 97 + fill="currentColor" 98 + fill-rule="evenodd" 99 + clip-rule="evenodd" 100 + d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z" 101 + /> 102 + </svg> 103 + Source Code 104 + </span> 105 + </a> 106 + <a 107 + href={"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md"} 108 + class="group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6" 109 + > 110 + <span class="absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105"> 111 + </span> 112 + <span class="relative flex items-center gap-4 sm:flex-col"> 113 + <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6"> 114 + <path 115 + d="M12 1v6M12 17v6" 116 + stroke="currentColor" 117 + stroke-width="2" 118 + stroke-linecap="round" 119 + stroke-linejoin="round" 120 + /> 121 + <circle 122 + cx="12" 123 + cy="12" 124 + r="4" 125 + fill="currentColor" 126 + fill-opacity=".15" 127 + stroke="currentColor" 128 + stroke-width="2" 129 + stroke-linecap="round" 130 + stroke-linejoin="round" 131 + /> 132 + </svg> 133 + Changelog 134 + </span> 135 + </a> 136 + </div> 137 + <div class="mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-base-content/80 sm:grid-cols-2"> 138 + <div> 139 + <a 140 + href="https://elixirforum.com" 141 + class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content" 142 + > 143 + <svg 144 + viewBox="0 0 16 16" 145 + aria-hidden="true" 146 + class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content" 147 + > 148 + <path d="M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z" /> 149 + </svg> 150 + Discuss on the Elixir Forum 151 + </a> 152 + </div> 153 + <div> 154 + <a 155 + href="https://discord.gg/elixir" 156 + class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content" 157 + > 158 + <svg 159 + viewBox="0 0 16 16" 160 + aria-hidden="true" 161 + class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content" 162 + > 163 + <path d="M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z" /> 164 + </svg> 165 + Join our Discord server 166 + </a> 167 + </div> 168 + <div> 169 + <a 170 + href="https://elixir-slack.community/" 171 + class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content" 172 + > 173 + <svg 174 + viewBox="0 0 16 16" 175 + aria-hidden="true" 176 + class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content" 177 + > 178 + <path d="M3.361 10.11a1.68 1.68 0 1 1-1.68-1.681h1.68v1.682ZM4.209 10.11a1.68 1.68 0 1 1 3.361 0v4.21a1.68 1.68 0 1 1-3.361 0v-4.21ZM5.89 3.361a1.68 1.68 0 1 1 1.681-1.68v1.68H5.89ZM5.89 4.209a1.68 1.68 0 1 1 0 3.361H1.68a1.68 1.68 0 1 1 0-3.361h4.21ZM12.639 5.89a1.68 1.68 0 1 1 1.68 1.681h-1.68V5.89ZM11.791 5.89a1.68 1.68 0 1 1-3.361 0V1.68a1.68 1.68 0 0 1 3.361 0v4.21ZM10.11 12.639a1.68 1.68 0 1 1-1.681 1.68v-1.68h1.682ZM10.11 11.791a1.68 1.68 0 1 1 0-3.361h4.21a1.68 1.68 0 1 1 0 3.361h-4.21Z" /> 179 + </svg> 180 + Join us on Slack 181 + </a> 182 + </div> 183 + <div> 184 + <a 185 + href="https://fly.io/docs/elixir/getting-started/" 186 + class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content" 187 + > 188 + <svg 189 + viewBox="0 0 20 20" 190 + aria-hidden="true" 191 + class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content" 192 + > 193 + <path d="M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z" /> 194 + </svg> 195 + Deploy your application 196 + </a> 197 + </div> 198 + </div> 199 + </div> 200 + </div> 201 + </div> 202 + </div>
+10
lib/comet_web/controllers/page_html.ex
··· 1 + defmodule CometWeb.PageHTML do 2 + @moduledoc """ 3 + This module contains pages rendered by PageController. 4 + 5 + See the `page_html` directory for all templates available. 6 + """ 7 + use CometWeb, :html 8 + 9 + embed_templates "page_html/*" 10 + end
+56
lib/comet_web/endpoint.ex
··· 1 + defmodule CometWeb.Endpoint do 2 + use Phoenix.Endpoint, otp_app: :comet 3 + 4 + # The session will be stored in the cookie and signed, 5 + # this means its contents can be read but not tampered with. 6 + # Set :encryption_salt if you would also like to encrypt it. 7 + @session_options [ 8 + store: :cookie, 9 + key: "_comet_key", 10 + signing_salt: "a/yCy9X7", 11 + same_site: "Lax" 12 + ] 13 + 14 + socket "/live", Phoenix.LiveView.Socket, 15 + websocket: [connect_info: [session: @session_options]], 16 + longpoll: [connect_info: [session: @session_options]] 17 + 18 + # Serve at "/" the static files from "priv/static" directory. 19 + # 20 + # When code reloading is disabled (e.g., in production), 21 + # the `gzip` option is enabled to serve compressed 22 + # static files generated by running `phx.digest`. 23 + plug Plug.Static, 24 + at: "/", 25 + from: :comet, 26 + gzip: not code_reloading?, 27 + only: ["hologram" | CometWeb.static_paths()], 28 + raise_on_missing_only: code_reloading? 29 + 30 + # Code reloading can be explicitly enabled under the 31 + # :code_reloader configuration of your endpoint. 32 + if code_reloading? do 33 + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 34 + plug Phoenix.LiveReloader 35 + plug Phoenix.CodeReloader 36 + plug Phoenix.Ecto.CheckRepoStatus, otp_app: :comet 37 + end 38 + 39 + plug Phoenix.LiveDashboard.RequestLogger, 40 + param_key: "request_logger", 41 + cookie_key: "request_logger" 42 + 43 + plug Plug.RequestId 44 + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 45 + 46 + plug Plug.Parsers, 47 + parsers: [:urlencoded, :multipart, :json], 48 + pass: ["*/*"], 49 + json_decoder: Phoenix.json_library() 50 + 51 + plug Plug.MethodOverride 52 + plug Plug.Head 53 + plug Plug.Session, @session_options 54 + plug Hologram.Router 55 + plug CometWeb.Router 56 + end
+25
lib/comet_web/gettext.ex
··· 1 + defmodule CometWeb.Gettext do 2 + @moduledoc """ 3 + A module providing Internationalization with a gettext-based API. 4 + 5 + By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations 6 + that you can use in your application. To use this Gettext backend module, 7 + call `use Gettext` and pass it as an option: 8 + 9 + use Gettext, backend: CometWeb.Gettext 10 + 11 + # Simple translation 12 + gettext("Here is the string to translate") 13 + 14 + # Plural translation 15 + ngettext("Here is the string to translate", 16 + "Here are the strings to translate", 17 + 3) 18 + 19 + # Domain-based translation 20 + dgettext("errors", "Here is the error message to translate") 21 + 22 + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 23 + """ 24 + use Gettext.Backend, otp_app: :comet 25 + end
+44
lib/comet_web/router.ex
··· 1 + defmodule CometWeb.Router do 2 + use CometWeb, :router 3 + 4 + pipeline :browser do 5 + plug :accepts, ["html"] 6 + plug :fetch_session 7 + plug :fetch_live_flash 8 + plug :put_root_layout, html: {CometWeb.Layouts, :root} 9 + plug :protect_from_forgery 10 + plug :put_secure_browser_headers 11 + end 12 + 13 + pipeline :api do 14 + plug :accepts, ["json"] 15 + end 16 + 17 + scope "/", CometWeb do 18 + pipe_through :browser 19 + 20 + get "/", PageController, :home 21 + end 22 + 23 + # Other scopes may use custom stacks. 24 + # scope "/api", CometWeb do 25 + # pipe_through :api 26 + # end 27 + 28 + # Enable LiveDashboard and Swoosh mailbox preview in development 29 + if Application.compile_env(:comet, :dev_routes) do 30 + # If you want to use the LiveDashboard in production, you should put 31 + # it behind authentication and allow only admins to access it. 32 + # If your application does not have an admins-only section yet, 33 + # you can use Plug.BasicAuth to set up some basic authentication 34 + # as long as you are also using SSL (which you should anyway). 35 + import Phoenix.LiveDashboard.Router 36 + 37 + scope "/dev" do 38 + pipe_through :browser 39 + 40 + live_dashboard "/dashboard", metrics: CometWeb.Telemetry 41 + forward "/mailbox", Plug.Swoosh.MailboxPreview 42 + end 43 + end 44 + end
+93
lib/comet_web/telemetry.ex
··· 1 + defmodule CometWeb.Telemetry do 2 + use Supervisor 3 + import Telemetry.Metrics 4 + 5 + def start_link(arg) do 6 + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 + end 8 + 9 + @impl true 10 + def init(_arg) do 11 + children = [ 12 + # Telemetry poller will execute the given period measurements 13 + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 + # Add reporters as children of your supervision tree. 16 + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 + ] 18 + 19 + Supervisor.init(children, strategy: :one_for_one) 20 + end 21 + 22 + def metrics do 23 + [ 24 + # Phoenix Metrics 25 + summary("phoenix.endpoint.start.system_time", 26 + unit: {:native, :millisecond} 27 + ), 28 + summary("phoenix.endpoint.stop.duration", 29 + unit: {:native, :millisecond} 30 + ), 31 + summary("phoenix.router_dispatch.start.system_time", 32 + tags: [:route], 33 + unit: {:native, :millisecond} 34 + ), 35 + summary("phoenix.router_dispatch.exception.duration", 36 + tags: [:route], 37 + unit: {:native, :millisecond} 38 + ), 39 + summary("phoenix.router_dispatch.stop.duration", 40 + tags: [:route], 41 + unit: {:native, :millisecond} 42 + ), 43 + summary("phoenix.socket_connected.duration", 44 + unit: {:native, :millisecond} 45 + ), 46 + sum("phoenix.socket_drain.count"), 47 + summary("phoenix.channel_joined.duration", 48 + unit: {:native, :millisecond} 49 + ), 50 + summary("phoenix.channel_handled_in.duration", 51 + tags: [:event], 52 + unit: {:native, :millisecond} 53 + ), 54 + 55 + # Database Metrics 56 + summary("comet.repo.query.total_time", 57 + unit: {:native, :millisecond}, 58 + description: "The sum of the other measurements" 59 + ), 60 + summary("comet.repo.query.decode_time", 61 + unit: {:native, :millisecond}, 62 + description: "The time spent decoding the data received from the database" 63 + ), 64 + summary("comet.repo.query.query_time", 65 + unit: {:native, :millisecond}, 66 + description: "The time spent executing the query" 67 + ), 68 + summary("comet.repo.query.queue_time", 69 + unit: {:native, :millisecond}, 70 + description: "The time spent waiting for a database connection" 71 + ), 72 + summary("comet.repo.query.idle_time", 73 + unit: {:native, :millisecond}, 74 + description: 75 + "The time the connection spent waiting before being checked out for the query" 76 + ), 77 + 78 + # VM Metrics 79 + summary("vm.memory.total", unit: {:byte, :kilobyte}), 80 + summary("vm.total_run_queue_lengths.total"), 81 + summary("vm.total_run_queue_lengths.cpu"), 82 + summary("vm.total_run_queue_lengths.io") 83 + ] 84 + end 85 + 86 + defp periodic_measurements do 87 + [ 88 + # A module, function and arguments to be invoked periodically. 89 + # This function must call :telemetry.execute/3 and a metric must be added above. 90 + # {CometWeb, :count_users, []} 91 + ] 92 + end 93 + end
+114
lib/comet_web.ex
··· 1 + defmodule CometWeb do 2 + @moduledoc """ 3 + The entrypoint for defining your web interface, such 4 + as controllers, components, channels, and so on. 5 + 6 + This can be used in your application as: 7 + 8 + use CometWeb, :controller 9 + use CometWeb, :html 10 + 11 + The definitions below will be executed for every controller, 12 + component, etc, so keep them short and clean, focused 13 + on imports, uses and aliases. 14 + 15 + Do NOT define functions inside the quoted expressions 16 + below. Instead, define additional modules and import 17 + those modules here. 18 + """ 19 + 20 + def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) 21 + 22 + def router do 23 + quote do 24 + use Phoenix.Router, helpers: false 25 + 26 + # Import common connection and controller functions to use in pipelines 27 + import Plug.Conn 28 + import Phoenix.Controller 29 + import Phoenix.LiveView.Router 30 + end 31 + end 32 + 33 + def channel do 34 + quote do 35 + use Phoenix.Channel 36 + end 37 + end 38 + 39 + def controller do 40 + quote do 41 + use Phoenix.Controller, formats: [:html, :json] 42 + 43 + use Gettext, backend: CometWeb.Gettext 44 + 45 + import Plug.Conn 46 + 47 + unquote(verified_routes()) 48 + end 49 + end 50 + 51 + def live_view do 52 + quote do 53 + use Phoenix.LiveView 54 + 55 + unquote(html_helpers()) 56 + end 57 + end 58 + 59 + def live_component do 60 + quote do 61 + use Phoenix.LiveComponent 62 + 63 + unquote(html_helpers()) 64 + end 65 + end 66 + 67 + def html do 68 + quote do 69 + use Phoenix.Component 70 + 71 + # Import convenience functions from controllers 72 + import Phoenix.Controller, 73 + only: [get_csrf_token: 0, view_module: 1, view_template: 1] 74 + 75 + # Include general helpers for rendering HTML 76 + unquote(html_helpers()) 77 + end 78 + end 79 + 80 + defp html_helpers do 81 + quote do 82 + # Translation 83 + use Gettext, backend: CometWeb.Gettext 84 + 85 + # HTML escaping functionality 86 + import Phoenix.HTML 87 + # Core UI components 88 + import CometWeb.CoreComponents 89 + 90 + # Common modules used in templates 91 + alias Phoenix.LiveView.JS 92 + alias CometWeb.Layouts 93 + 94 + # Routes generation with the ~p sigil 95 + unquote(verified_routes()) 96 + end 97 + end 98 + 99 + def verified_routes do 100 + quote do 101 + use Phoenix.VerifiedRoutes, 102 + endpoint: CometWeb.Endpoint, 103 + router: CometWeb.Router, 104 + statics: CometWeb.static_paths() 105 + end 106 + end 107 + 108 + @doc """ 109 + When used, dispatch to the appropriate controller/live_view/etc. 110 + """ 111 + defmacro __using__(which) when is_atom(which) do 112 + apply(__MODULE__, which, []) 113 + end 114 + end
+103
mix.exs
··· 1 + defmodule Comet.MixProject do 2 + use Mix.Project 3 + 4 + def project do 5 + [ 6 + app: :comet, 7 + version: "0.1.0", 8 + elixir: "~> 1.15", 9 + elixirc_paths: elixirc_paths(Mix.env()), 10 + start_permanent: Mix.env() == :prod, 11 + aliases: aliases(), 12 + deps: deps(), 13 + compilers: [:phoenix_live_view, :hologram] ++ Mix.compilers(), 14 + listeners: [Phoenix.CodeReloader] 15 + ] 16 + end 17 + 18 + # Configuration for the OTP application. 19 + # 20 + # Type `mix help compile.app` for more information. 21 + def application do 22 + [ 23 + mod: {Comet.Application, []}, 24 + extra_applications: [:logger, :runtime_tools] 25 + ] 26 + end 27 + 28 + def cli do 29 + [ 30 + preferred_envs: [precommit: :test] 31 + ] 32 + end 33 + 34 + # Specifies which paths to compile per environment. 35 + defp elixirc_paths(:test), do: ["lib", "test/support"] 36 + defp elixirc_paths(_), do: ["lib"] 37 + 38 + # Specifies your project dependencies. 39 + # 40 + # Type `mix help deps` for examples and options. 41 + defp deps do 42 + [ 43 + {:phoenix, "~> 1.8.2"}, 44 + {:phoenix_ecto, "~> 4.5"}, 45 + {:ecto_sql, "~> 3.13"}, 46 + {:postgrex, ">= 0.0.0"}, 47 + {:phoenix_html, "~> 4.1"}, 48 + {:phoenix_live_reload, "~> 1.2", only: :dev}, 49 + {:phoenix_live_view, "~> 1.1.0"}, 50 + {:lazy_html, ">= 0.1.0", only: :test}, 51 + {:phoenix_live_dashboard, "~> 0.8.3"}, 52 + {:esbuild, "~> 0.10", runtime: Mix.env() == :dev}, 53 + {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}, 54 + {:heroicons, 55 + github: "tailwindlabs/heroicons", 56 + tag: "v2.2.0", 57 + sparse: "optimized", 58 + app: false, 59 + compile: false, 60 + depth: 1}, 61 + {:swoosh, "~> 1.16"}, 62 + {:req, "~> 0.5"}, 63 + {:telemetry_metrics, "~> 1.0"}, 64 + {:telemetry_poller, "~> 1.0"}, 65 + {:gettext, "~> 1.0"}, 66 + {:jason, "~> 1.2"}, 67 + {:dns_cluster, "~> 0.2.0"}, 68 + {:bandit, "~> 1.5"}, 69 + {:atex, "~> 0.6"}, 70 + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, 71 + {:drinkup, "~> 0.1"}, 72 + {:typedstruct, "~> 0.5"}, 73 + {:hologram, "~> 0.6.5"} 74 + ] 75 + end 76 + 77 + # Aliases are shortcuts or tasks specific to the current project. 78 + # For example, to install project dependencies and perform other setup tasks, run: 79 + # 80 + # $ mix setup 81 + # 82 + # See the documentation for `Mix` for more info on aliases. 83 + defp aliases do 84 + [ 85 + setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"], 86 + "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 87 + "ecto.reset": ["ecto.drop", "ecto.setup"], 88 + test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], 89 + "assets.setup": [ 90 + "cmd pnpm install", 91 + # "tailwind.install --if-missing", 92 + "esbuild.install --if-missing" 93 + ], 94 + "assets.build": ["compile", "tailwind comet", "esbuild comet"], 95 + "assets.deploy": [ 96 + "tailwind comet --minify", 97 + "esbuild comet --minify", 98 + "phx.digest" 99 + ], 100 + precommit: ["compile --warnings-as-errors", "deps.unlock --unused", "format", "test"] 101 + ] 102 + end 103 + end
+69
mix.lock
··· 1 + %{ 2 + "atex": {:hex, :atex, "0.6.0", "a02f3c1b3ef04d8cd30243a05fc3629929c66d826e1d6a7e9d4ec6076ac89aea", [:mix], [{:ex_cldr, "~> 2.42", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}, {:multiformats_ex, "~> 0.2", [hex: :multiformats_ex, repo: "hexpm", optional: false]}, {:peri, "~> 0.6", [hex: :peri, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:recase, "~> 0.5", [hex: :recase, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}], "hexpm", "a3615e361e1e1b2887910834b3ede88680a02e4d585243ea90d0a6394f688aa2"}, 3 + "bandit": {:hex, :bandit, "1.8.0", "c2e93d7e3c5c794272fa4623124f827c6f24b643acc822be64c826f9447d92fb", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8458ff4eed20ff2a2ea69d4854883a077c33ea42b51f6811b044ceee0fa15422"}, 4 + "beam_file": {:hex, :beam_file, "0.6.2", "efd54ec60be6a03f0a8f96f72b0353427196613289c46032d3500f0ab6c34d32", [:mix], [], "hexpm", "09a99e8e5aad674edcad7213b0d7602375dfd3c7d02f8e3136e3efae0bcc9c56"}, 5 + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 6 + "car": {:hex, :car, "0.1.1", "a5bc4c5c1be96eab437634b3c0ccad1fe17b5e3d68c22a4031241ae1345aebd4", [:mix], [{:cbor, "~> 1.0.0", [hex: :cbor, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}, {:varint, "~> 1.4", [hex: :varint, repo: "hexpm", optional: false]}], "hexpm", "f895dda8123d04dd336db5a2bf0d0b47f4559cd5383f83fcca0700c1b45bfb6a"}, 7 + "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, 8 + "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, 9 + "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, 10 + "cldr_utils": {:hex, :cldr_utils, "2.29.1", "11ff0a50a36a7e5f3bd9fc2fb8486a4c1bcca3081d9c080bf9e48fe0e6742e2d", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3844a0a0ed7f42e6590ddd8bd37eb4b1556b112898f67dea3ba068c29aabd6c2"}, 11 + "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, 12 + "credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [: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", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"}, 13 + "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, 14 + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, 15 + "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, 16 + "drinkup": {:hex, :drinkup, "0.1.0", "a21d563163a0a19db448820f0c4cf9278d52e94b7276b099c60cb8544b16b14e", [:mix], [{:car, "~> 0.1.0", [hex: :car, repo: "hexpm", optional: false]}, {:cbor, "~> 1.0.0", [hex: :cbor, repo: "hexpm", optional: false]}, {:certifi, "~> 2.15", [hex: :certifi, repo: "hexpm", optional: false]}, {:gun, "~> 2.2", [hex: :gun, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}], "hexpm", "e2a22b12386936f4a62c6307050c3484c41ecd07c50dc1b1af653195d539baa2"}, 17 + "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 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", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, 18 + "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, 19 + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, 20 + "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, 21 + "ex_cldr": {:hex, :ex_cldr, "2.44.1", "0d220b175874e1ce77a0f7213bdfe700b9be11aefbf35933a0e98837803ebdc5", [:mix], [{:cldr_utils, "~> 2.28", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19 or ~> 1.0", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "3880cd6137ea21c74250cd870d3330c4a9fdec07fabd5e37d1b239547929e29b"}, 22 + "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, 23 + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, 24 + "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, 25 + "fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"}, 26 + "gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"}, 27 + "gproc": {:hex, :gproc, "1.0.0", "aa9ec57f6c9ff065b16d96924168d7c7157cd1fd457680efe4b1274f456fa500", [:rebar3], [], "hexpm", "109f253c2787de8a371a51179d4973230cbec6239ee673fa12216a5ce7e4f902"}, 28 + "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"}, 29 + "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]}, 30 + "hologram": {:hex, :hologram, "0.6.5", "355312e414489f590520efa2ccccf1719c415793225a87330407772efcaa0a78", [:mix], [{:beam_file, "0.6.2", [hex: :beam_file, repo: "hexpm", optional: false]}, {:file_system, "~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:gproc, "~> 1.0", [hex: :gproc, repo: "hexpm", optional: false]}, {:html_entities, "~> 0.5", [hex: :html_entities, repo: "hexpm", optional: false]}, {:interceptor, "~> 0.5", [hex: :interceptor, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:uuid, "~> 1.0", [hex: :uuid, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8b351690087b9980e60d4a2992cfceb4637e2a2082afdfc78060f569299abdc6"}, 31 + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, 32 + "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, 33 + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 34 + "interceptor": {:hex, :interceptor, "0.5.4", "158e019413439714306d52ff5189533b1236ab776b6f7a1ce63ace079f84867c", [:mix], [], "hexpm", "a4fa78a73199832222c4bb47c57e5b7384f7b49d43c09a57c6e731086f219f95"}, 35 + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 36 + "jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"}, 37 + "lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"}, 38 + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, 39 + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, 40 + "multiformats_ex": {:hex, :multiformats_ex, "0.2.0", "5b0a3faa1a770dc671aa8a89b6323cc20b0ecf67dc93dcd21312151fbea6b4ee", [:mix], [{:varint, "~> 1.4", [hex: :varint, repo: "hexpm", optional: false]}], "hexpm", "aa406d9addb06dc197e0e92212992486af6599158d357680f29f2d11e08d0423"}, 41 + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, 42 + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, 43 + "peri": {:hex, :peri, "0.6.2", "3c043bfb6aa18eb1ea41d80981d19294c5e943937b1311e8e958da3581139061", [:mix], [{:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.1", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "5e0d8e0bd9de93d0f8e3ad6b9a5bd143f7349c025196ef4a3591af93ce6ecad9"}, 44 + "phoenix": {:hex, :phoenix, "1.8.2", "75aba5b90081d88a54f2fc6a26453d4e76762ab095ff89be5a3e7cb28bff9300", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {: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", "19ea65b4064f17b1ab0515595e4d0ea65742ab068259608d5d7b139a73f47611"}, 45 + "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, 46 + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, 47 + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [: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]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, 48 + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.1", "05df733a09887a005ed0d69a7fc619d376aea2730bf64ce52ac51ce716cc1ef0", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "74273843d5a6e4fef0bbc17599f33e3ec63f08e69215623a0cd91eea4288e5a0"}, 49 + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.18", "b5410017b3d4edf261d9c98ebc334e0637d7189457c730720cfc13e206443d43", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f189b759595feff0420e9a1d544396397f9cf9e2d5a8cb98ba5b6cab01927da0"}, 50 + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, 51 + "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"}, 52 + "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [: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", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, 53 + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, 54 + "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [: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", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, 55 + "recase": {:hex, :recase, "0.9.1", "82d2e2e2d4f9e92da1ce5db338ede2e4f15a50ac1141fc082b80050b9f49d96e", [:mix], [], "hexpm", "19ba03ceb811750e6bec4a015a9f9e45d16a8b9e09187f6d72c3798f454710f3"}, 56 + "req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"}, 57 + "swoosh": {:hex, :swoosh, "1.19.8", "0576f2ea96d1bb3a6e02cc9f79cbd7d497babc49a353eef8dce1a1f9f82d7915", [: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]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {: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]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d7503c2daf0f9899afd8eba9923eeddef4b62e70816e1d3b6766e4d6c60e94ad"}, 58 + "tailwind": {:hex, :tailwind, "0.4.1", "e7bcc222fe96a1e55f948e76d13dd84a1a7653fb051d2a167135db3b4b08d3e9", [:mix], [], "hexpm", "6249d4f9819052911120dbdbe9e532e6bd64ea23476056adb7f730aa25c220d1"}, 59 + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, 60 + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, 61 + "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, 62 + "thousand_island": {:hex, :thousand_island, "1.4.2", "735fa783005d1703359bbd2d3a5a3a398075ba4456e5afe3c5b7cf4666303d36", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1c7637f16558fc1c35746d5ee0e83b18b8e59e18d28affd1f2fa1645f8bc7473"}, 63 + "typedstruct": {:hex, :typedstruct, "0.5.4", "d1d33d58460a74f413e9c26d55e66fd633abd8ac0fb12639add9a11a60a0462a", [:make, :mix], [], "hexpm", "ffaef36d5dbaebdbf4ed07f7fb2ebd1037b2c1f757db6fb8e7bcbbfabbe608d8"}, 64 + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, 65 + "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, 66 + "varint": {:hex, :varint, "1.5.1", "17160c70d0428c3f8a7585e182468cac10bbf165c2360cf2328aaa39d3fb1795", [:mix], [], "hexpm", "24f3deb61e91cb988056de79d06f01161dd01be5e0acae61d8d936a552f1be73"}, 67 + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, 68 + "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [: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", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, 69 + }
+9 -14
package.json
··· 2 2 "name": "@comet/workspace", 3 3 "version": "1.0.0", 4 4 "devDependencies": { 5 - "@types/bun": "latest", 6 - "pino-pretty": "^13.0.0", 5 + "@tailwindcss/cli": "^4.1.17", 7 6 "prettier": "^3.5.3", 8 - "prettier-plugin-svelte": "^3.4.0", 9 - "prettier-plugin-tailwindcss": "^0.6.11" 10 - }, 11 - "peerDependencies": { 7 + "prettier-plugin-tailwindcss": "^0.6.11", 8 + "tailwindcss": "^4.1.17", 12 9 "typescript": "^5" 13 10 }, 14 11 "private": true, 15 12 "scripts": { 16 - "format": "prettier --write .", 17 - "lint": "prettier --check . && eslint .", 18 - "lexicons": "bun --filter @comet/lexicons generate" 13 + "format": "prettier --write ." 19 14 }, 20 - "type": "module", 21 - "workspaces": [ 22 - "apps/*", 23 - "packages/*" 24 - ] 15 + "type": "commonjs", 16 + "workspaces": [], 17 + "dependencies": { 18 + "@fontsource-variable/work-sans": "^5.2.8" 19 + } 25 20 }
-34
packages/lexicons/.gitignore
··· 1 - # dependencies (bun install) 2 - node_modules 3 - 4 - # output 5 - out 6 - dist 7 - *.tgz 8 - 9 - # code coverage 10 - coverage 11 - *.lcov 12 - 13 - # logs 14 - logs 15 - _.log 16 - report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 - 18 - # dotenv environment variable files 19 - .env 20 - .env.development.local 21 - .env.test.local 22 - .env.production.local 23 - .env.local 24 - 25 - # caches 26 - .eslintcache 27 - .cache 28 - *.tsbuildinfo 29 - 30 - # IntelliJ based IDEs 31 - .idea 32 - 33 - # Finder (MacOS) folder config 34 - .DS_Store
packages/lexicons/README.md

This is a binary file and will not be displayed.

-28
packages/lexicons/defs/sh/comet/v0/actor/getProfile.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.actor.getProfile", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get the profile view of an actor.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["actor"], 11 - "properties": { 12 - "actor": { 13 - "type": "string", 14 - "format": "at-identifier", 15 - "description": "Handle or DID of account to fetch profile of." 16 - } 17 - } 18 - }, 19 - "output": { 20 - "encoding": "application/json", 21 - "schema": { 22 - "type": "ref", 23 - "ref": "sh.comet.v0.actor.profile#view" 24 - } 25 - } 26 - } 27 - } 28 - }
-37
packages/lexicons/defs/sh/comet/v0/actor/getProfiles.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.actor.getProfiles", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get the profile views of multiple actors.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["actors"], 11 - "properties": { 12 - "actors": { 13 - "type": "array", 14 - "maxLength": 25, 15 - "items": { "type": "string", "format": "at-identifier" } 16 - } 17 - } 18 - }, 19 - "output": { 20 - "encoding": "application/json", 21 - "schema": { 22 - "type": "object", 23 - "required": ["profiles"], 24 - "properties": { 25 - "profiles": { 26 - "type": "array", 27 - "items": { 28 - "type": "ref", 29 - "ref": "sh.comet.v0.actor.profile#view" 30 - } 31 - } 32 - } 33 - } 34 - } 35 - } 36 - } 37 - }
-109
packages/lexicons/defs/sh/comet/v0/actor/profile.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.actor.profile", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "A user's Comet profile.", 8 - "key": "literal:self", 9 - "record": { 10 - "type": "object", 11 - "properties": { 12 - "displayName": { 13 - "type": "string", 14 - "maxGraphemes": 64, 15 - "maxLength": 640 16 - }, 17 - "description": { 18 - "type": "string", 19 - "description": "Free-form profile description text.", 20 - "maxGraphemes": 256, 21 - "maxLength": 2560 22 - }, 23 - "descriptionFacets": { 24 - "type": "ref", 25 - "description": "Annotations of the user's description.", 26 - "ref": "sh.comet.v0.richtext.facet" 27 - }, 28 - "avatar": { 29 - "type": "blob", 30 - "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", 31 - "accept": ["image/png", "image/jpeg"], 32 - "maxSize": 1000000 33 - }, 34 - "banner": { 35 - "type": "blob", 36 - "description": "Larger horizontal image to display behind profile view.", 37 - "accept": ["image/png", "image/jpeg"], 38 - "maxSize": 1000000 39 - }, 40 - "featuredItems": { 41 - "type": "array", 42 - "description": "Pinned items to be shown first on the user's profile.", 43 - "maxLength": 5, 44 - "items": { "type": "string", "format": "at-uri" } 45 - }, 46 - "createdAt": { "type": "string", "format": "datetime" } 47 - } 48 - } 49 - }, 50 - "view": { 51 - "type": "object", 52 - "required": ["did", "handle"], 53 - "properties": { 54 - "did": { "type": "string", "format": "did" }, 55 - "handle": { "type": "string", "format": "handle" }, 56 - "displayName": { 57 - "type": "string", 58 - "maxGraphemes": 64, 59 - "maxLength": 640 60 - }, 61 - "avatar": { "type": "string", "format": "uri" }, 62 - "indexedAt": { "type": "string", "format": "datetime" }, 63 - "createdAt": { "type": "string", "format": "datetime" }, 64 - "viewer": { "type": "ref", "ref": "#viewerState" } 65 - } 66 - }, 67 - "viewFull": { 68 - "type": "object", 69 - "required": ["did", "handle"], 70 - "properties": { 71 - "did": { "type": "string", "format": "did" }, 72 - "handle": { "type": "string", "format": "handle" }, 73 - "displayName": { 74 - "type": "string", 75 - "maxGraphemes": 64, 76 - "maxLength": 640 77 - }, 78 - "description": { 79 - "type": "string", 80 - "maxGraphemes": 256, 81 - "maxLength": 2560 82 - }, 83 - "descriptionFacets": { 84 - "type": "ref", 85 - "ref": "sh.comet.v0.richtext.facet" 86 - }, 87 - "avatar": { "type": "string", "format": "uri" }, 88 - "banner": { "type": "string", "format": "uri" }, 89 - "followersCount": { "type": "integer" }, 90 - "followsCount": { "type": "integer" }, 91 - "tracksCount": { "type": "integer" }, 92 - "playlistsCount": { "type": "integer" }, 93 - "indexedAt": { "type": "string", "format": "datetime" }, 94 - "createdAt": { "type": "string", "format": "datetime" }, 95 - "featuredItems": { 96 - "type": "array", 97 - "maxLength": 5, 98 - "items": { "type": "string", "format": "at-uri" } 99 - }, 100 - "viewer": { "type": "ref", "ref": "#viewerState" } 101 - } 102 - }, 103 - "viewerState": { 104 - "type": "object", 105 - "description": "Metadata about the requesting account's relationship with the user. TODO: determine if we create our own graph or inherit bsky's.", 106 - "properties": {} 107 - } 108 - } 109 - }
-47
packages/lexicons/defs/sh/comet/v0/feed/comment.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.comment", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "A comment on a piece of Comet media.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["text", "subject", "createdAt"], 12 - "properties": { 13 - "text": { 14 - "type": "string", 15 - "minLength": 1, 16 - "minGraphemes": 1, 17 - "maxLength": 3000, 18 - "maxGraphemes": 300, 19 - "description": "The comment's content." 20 - }, 21 - "subject": { 22 - "type": "string", 23 - "format": "at-uri", 24 - "description": "The piece of media being commented on." 25 - }, 26 - "reply": { 27 - "type": "string", 28 - "format": "at-uri", 29 - "description": "Another sh.comet.v0.feed.comment this is in reply to." 30 - }, 31 - "langs": { 32 - "type": "array", 33 - "description": "Indicates human language of the comment's text content.", 34 - "maxLength": 3, 35 - "items": { "type": "string", "format": "language" } 36 - }, 37 - "facets": { 38 - "type": "array", 39 - "description": "Annotations of text (mentions, URLs, hashtags, etc)", 40 - "items": { "type": "ref", "ref": "sh.comet.v0.richtext.facet" } 41 - }, 42 - "createdAt": { "type": "string", "format": "datetime" } 43 - } 44 - } 45 - } 46 - } 47 - }
-38
packages/lexicons/defs/sh/comet/v0/feed/defs.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.defs", 4 - "defs": { 5 - "link": { 6 - "type": "object", 7 - "description": "Link for the track. Usually to acquire it in some way, e.g. via free download or purchase. | TODO: multiple links?", 8 - "required": ["type", "value"], 9 - "properties": { 10 - "type": { 11 - "type": "string", 12 - "knownValues": [ 13 - "sh.comet.v0.feed.defs#downloadLink", 14 - "sh.comet.v0.feed.defs#buyLink" 15 - ] 16 - }, 17 - "value": { "type": "string", "format": "uri" } 18 - } 19 - }, 20 - "downloadLink": { 21 - "type": "token", 22 - "description": "Indicate the link leads to a free download for the track." 23 - }, 24 - "buyLink": { 25 - "type": "token", 26 - "description": "Indicate the link leads to a purchase page for the track." 27 - }, 28 - "viewerState": { 29 - "type": "object", 30 - "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", 31 - "properties": { 32 - "like": { "type": "string", "format": "at-uri" }, 33 - "repost": { "type": "string", "format": "at-uri" }, 34 - "featured": { "type": "boolean" } 35 - } 36 - } 37 - } 38 - }
-44
packages/lexicons/defs/sh/comet/v0/feed/getActorPlaylists.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.getActorPlaylists", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a list of an actor's playlists.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["actor"], 11 - "properties": { 12 - "actor": { 13 - "type": "string", 14 - "format": "at-identifier" 15 - }, 16 - "limit": { 17 - "type": "integer", 18 - "minimum": 1, 19 - "maximum": 100, 20 - "default": 50 21 - }, 22 - "cursor": { "type": "string" } 23 - } 24 - }, 25 - "output": { 26 - "encoding": "application/json", 27 - "schema": { 28 - "type": "object", 29 - "required": ["playlists"], 30 - "properties": { 31 - "cursor": { "type": "string" }, 32 - "playlists": { 33 - "type": "array", 34 - "items": { 35 - "type": "ref", 36 - "ref": "sh.comet.v0.feed.playlist#view" 37 - } 38 - } 39 - } 40 - } 41 - } 42 - } 43 - } 44 - }
-44
packages/lexicons/defs/sh/comet/v0/feed/getActorTracks.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.getActorTracks", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "description": "Get a list of an actor's tracks.", 8 - "parameters": { 9 - "type": "params", 10 - "required": ["actor"], 11 - "properties": { 12 - "actor": { 13 - "type": "string", 14 - "format": "at-identifier" 15 - }, 16 - "limit": { 17 - "type": "integer", 18 - "minimum": 1, 19 - "maximum": 100, 20 - "default": 50 21 - }, 22 - "cursor": { "type": "string" } 23 - } 24 - }, 25 - "output": { 26 - "encoding": "application/json", 27 - "schema": { 28 - "type": "object", 29 - "required": ["tracks"], 30 - "properties": { 31 - "cursor": { "type": "string" }, 32 - "tracks": { 33 - "type": "array", 34 - "items": { 35 - "type": "ref", 36 - "ref": "sh.comet.v0.feed.track#view" 37 - } 38 - } 39 - } 40 - } 41 - } 42 - } 43 - } 44 - }
-19
packages/lexicons/defs/sh/comet/v0/feed/like.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.like", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "Record representing a 'like' of some media. Weakly linked with just an at-uri.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["subject", "createdAt"], 12 - "properties": { 13 - "subject": { "type": "string", "format": "at-uri" }, 14 - "createdAt": { "type": "string", "format": "datetime" } 15 - } 16 - } 17 - } 18 - } 19 - }
-19
packages/lexicons/defs/sh/comet/v0/feed/play.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.play", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "Record representing a 'play' of some media.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["subject", "createdAt"], 12 - "properties": { 13 - "subject": { "type": "string", "format": "at-uri" }, 14 - "createdAt": { "type": "string", "format": "datetime" } 15 - } 16 - } 17 - } 18 - } 19 - }
-114
packages/lexicons/defs/sh/comet/v0/feed/playlist.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.playlist", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "A Comet playlist, containing many audio tracks.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["title", "type", "createdAt"], 12 - "properties": { 13 - "image": { 14 - "type": "blob", 15 - "description": "Image to be displayed representing the playlist.", 16 - "accept": ["image/png", "image/jpeg"], 17 - "maxSize": 1000000 18 - }, 19 - "title": { 20 - "type": "string", 21 - "description": "Title of the playlist.", 22 - "minLength": 1, 23 - "maxLength": 2560, 24 - "maxGraphemes": 256 25 - }, 26 - "description": { 27 - "type": "string", 28 - "description": "Description of the playlist.", 29 - "maxLength": 20000, 30 - "maxGraphemes": 2000 31 - }, 32 - "descriptionFacets": { 33 - "type": "ref", 34 - "description": "Annotations of the playlist's description.", 35 - "ref": "sh.comet.v0.richtext.facet" 36 - }, 37 - "type": { 38 - "type": "string", 39 - "description": "Type of the playlist. Allows differentiating between playlist's different purposes.", 40 - "knownValues": [ 41 - "sh.comet.v0.feed.playlist#album", 42 - "sh.comet.v0.feed.playlist#compilation", 43 - "sh.comet.v0.feed.playlist#playlist", 44 - "sh.comet.v0.feed.playlist#podcast" 45 - ] 46 - }, 47 - "tags": { 48 - "type": "array", 49 - "description": "Hashtags for the playlist, usually for genres.", 50 - "maxLength": 8, 51 - "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 52 - }, 53 - "link": { 54 - "type": "ref", 55 - "ref": "sh.comet.v0.feed.defs#link" 56 - }, 57 - "createdAt": { 58 - "type": "string", 59 - "format": "datetime", 60 - "description": "Timestamp for when the playlist was originally created." 61 - } 62 - } 63 - } 64 - }, 65 - "album": { 66 - "type": "token", 67 - "description": "Indicates the playlist is an album or extended play." 68 - }, 69 - "compilation": { 70 - "type": "token", 71 - "description": "Indicates the playlist is a compilation of various tracks, usually put together by a label." 72 - }, 73 - "playlist": { 74 - "type": "token", 75 - "description": "Indicates the playlist is a miscellaneous list of tracks." 76 - }, 77 - "podcast": { 78 - "type": "token", 79 - "description": "Indicates the playlist is a podcast or radio show split into several individual tracks." 80 - }, 81 - "view": { 82 - "type": "object", 83 - "required": ["uri", "cid", "author", "tracks", "record"], 84 - "properties": { 85 - "uri": { "type": "string", "format": "at-uri" }, 86 - "cid": { "type": "string", "format": "cid" }, 87 - "author": { 88 - "type": "ref", 89 - "ref": "sh.comet.v0.actor.profile#viewFull" 90 - }, 91 - "tracks": { 92 - "type": "array", 93 - "items": { "type": "ref", "ref": "sh.comet.v0.feed.track#view" }, 94 - "description": "TODO: include cursor for pagination if too many?" 95 - }, 96 - "image": { 97 - "type": "string", 98 - "format": "uri", 99 - "description": "URL pointing to where the image for the playlist can be fetched." 100 - }, 101 - "record": { 102 - "type": "ref", 103 - "ref": "#main" 104 - }, 105 - "repostCount": { "type": "integer" }, 106 - "likeCount": { "type": "integer" }, 107 - "commentCount": { "type": "integer" }, 108 - "trackCount": { "type": "integer" }, 109 - "indexedAt": { "type": "string", "format": "datetime" }, 110 - "viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" } 111 - } 112 - } 113 - } 114 - }
-31
packages/lexicons/defs/sh/comet/v0/feed/playlistTrack.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.playlistTrack", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "A link between a Comet track and a playlist.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["track", "playlist", "position", "createdAt"], 12 - "properties": { 13 - "track": { 14 - "type": "string", 15 - "format": "at-uri" 16 - }, 17 - "playlist": { 18 - "type": "string", 19 - "format": "at-uri" 20 - }, 21 - "position": { "type": "integer", "minimum": 0 }, 22 - "createdAt": { 23 - "type": "string", 24 - "format": "datetime", 25 - "description": "Timestamp for when the track entry was originally added to the playlist." 26 - } 27 - } 28 - } 29 - } 30 - } 31 - }
-19
packages/lexicons/defs/sh/comet/v0/feed/repost.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.repost", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "Record representing a 'repost' of some media. Weakly linked with just an at-uri.", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["subject", "createdAt"], 12 - "properties": { 13 - "subject": { "type": "string", "format": "at-uri" }, 14 - "createdAt": { "type": "string", "format": "datetime" } 15 - } 16 - } 17 - } 18 - } 19 - }
-103
packages/lexicons/defs/sh/comet/v0/feed/track.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.feed.track", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "description": "A Comet audio track. TODO: should probably have some sort of pre-calculated waveform, or have a query to get one from a blob?", 8 - "key": "tid", 9 - "record": { 10 - "type": "object", 11 - "required": ["audio", "title", "createdAt"], 12 - "properties": { 13 - "audio": { 14 - "type": "blob", 15 - "description": "Audio of the track, ideally encoded as 96k Opus. Limited to 100mb.", 16 - "accept": ["audio/ogg"], 17 - "maxSize": 100000000 18 - }, 19 - "image": { 20 - "type": "blob", 21 - "description": "Image to be displayed representing the track.", 22 - "accept": ["image/png", "image/jpeg"], 23 - "maxSize": 1000000 24 - }, 25 - "title": { 26 - "type": "string", 27 - "description": "Title of the track. Usually shouldn't include the creator's name.", 28 - "minLength": 1, 29 - "maxLength": 2560, 30 - "maxGraphemes": 256 31 - }, 32 - "description": { 33 - "type": "string", 34 - "description": "Description of the track.", 35 - "maxLength": 20000, 36 - "maxGraphemes": 2000 37 - }, 38 - "descriptionFacets": { 39 - "type": "ref", 40 - "description": "Annotations of the track's description.", 41 - "ref": "sh.comet.v0.richtext.facet" 42 - }, 43 - "explicit": { 44 - "type": "boolean", 45 - "description": "Whether the track contains explicit content that may objectionable to some people, usually swearing or adult themes." 46 - }, 47 - "tags": { 48 - "type": "array", 49 - "description": "Hashtags for the track, usually for genres.", 50 - "maxLength": 8, 51 - "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 52 - }, 53 - "link": { 54 - "type": "ref", 55 - "ref": "sh.comet.v0.feed.defs#link" 56 - }, 57 - "createdAt": { 58 - "type": "string", 59 - "format": "datetime", 60 - "description": "Timestamp for when the track entry was originally created." 61 - }, 62 - "releasedAt": { 63 - "type": "string", 64 - "format": "datetime", 65 - "description": "Timestamp for when the track was released. If in the future, may be used to implement pre-savable tracks." 66 - } 67 - } 68 - } 69 - }, 70 - "view": { 71 - "type": "object", 72 - "required": ["uri", "cid", "author", "audio", "record", "indexedAt"], 73 - "properties": { 74 - "uri": { "type": "string", "format": "at-uri" }, 75 - "cid": { "type": "string", "format": "cid" }, 76 - "author": { 77 - "type": "ref", 78 - "ref": "sh.comet.v0.actor.profile#viewFull" 79 - }, 80 - "audio": { 81 - "type": "string", 82 - "format": "uri", 83 - "description": "URL pointing to where the audio data for the track can be fetched. May be re-encoded from the original blob." 84 - }, 85 - "image": { 86 - "type": "string", 87 - "format": "uri", 88 - "description": "URL pointing to where the image for the track can be fetched." 89 - }, 90 - "record": { 91 - "type": "ref", 92 - "ref": "#main" 93 - }, 94 - "repostCount": { "type": "integer" }, 95 - "likeCount": { "type": "integer" }, 96 - "playCount": { "type": "integer" }, 97 - "commentCount": { "type": "integer" }, 98 - "indexedAt": { "type": "string", "format": "datetime" }, 99 - "viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" } 100 - } 101 - } 102 - } 103 - }
-62
packages/lexicons/defs/sh/comet/v0/richtext/facet.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.comet.v0.richtext.facet", 4 - "defs": { 5 - "main": { 6 - "type": "object", 7 - "description": "Annotation of a sub-string within rich text.", 8 - "required": ["index", "features"], 9 - "properties": { 10 - "index": { "type": "ref", "ref": "#byteSlice" }, 11 - "features": { 12 - "type": "array", 13 - "items": { "type": "union", "refs": ["#mention", "#link", "#tag"] } 14 - } 15 - } 16 - }, 17 - "mention": { 18 - "type": "object", 19 - "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", 20 - "required": ["did"], 21 - "properties": { 22 - "did": { "type": "string", "format": "did" } 23 - } 24 - }, 25 - "link": { 26 - "type": "object", 27 - "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.", 28 - "required": ["uri"], 29 - "properties": { 30 - "uri": { "type": "string", "format": "uri" } 31 - } 32 - }, 33 - "tag": { 34 - "type": "object", 35 - "description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", 36 - "required": ["tag"], 37 - "properties": { 38 - "tag": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } 39 - } 40 - }, 41 - "timestamp": { 42 - "type": "object", 43 - "description": "Facet feature for a timestamp in a track. The text usually is in the format of 'hh:mm:ss' with the hour section being omitted if unnecessary.", 44 - "properties": { 45 - "timestamp": { 46 - "type": "integer", 47 - "minimum": 0, 48 - "description": "Reference time, in seconds." 49 - } 50 - } 51 - }, 52 - "byteSlice": { 53 - "type": "object", 54 - "description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.", 55 - "required": ["byteStart", "byteEnd"], 56 - "properties": { 57 - "byteStart": { "type": "integer", "minimum": 0 }, 58 - "byteEnd": { "type": "integer", "minimum": 0 } 59 - } 60 - } 61 - } 62 - }
-6
packages/lexicons/lex-cli.config.js
··· 1 - import { defineLexiconConfig } from "@atcute/lex-cli"; 2 - 3 - export default defineLexiconConfig({ 4 - files: ["./defs/sh/comet/v0/**/*.json"], 5 - outdir: "./src", 6 - });
-19
packages/lexicons/package.json
··· 1 - { 2 - "name": "@comet/lexicons", 3 - "main": "src/index.ts", 4 - "devDependencies": { 5 - "@atcute/lex-cli": "^2.0.2", 6 - "@types/bun": "latest" 7 - }, 8 - "peerDependencies": { 9 - "typescript": "^5" 10 - }, 11 - "private": true, 12 - "scripts": { 13 - "generate": "lex-cli generate -c ./lex-cli.config.js" 14 - }, 15 - "type": "module", 16 - "dependencies": { 17 - "@atcute/lexicons": "^1.0.3" 18 - } 19 - }
-14
packages/lexicons/src/index.ts
··· 1 - export * as ShCometV0ActorGetProfile from "./types/sh/comet/v0/actor/getProfile.js"; 2 - export * as ShCometV0ActorGetProfiles from "./types/sh/comet/v0/actor/getProfiles.js"; 3 - export * as ShCometV0ActorProfile from "./types/sh/comet/v0/actor/profile.js"; 4 - export * as ShCometV0FeedComment from "./types/sh/comet/v0/feed/comment.js"; 5 - export * as ShCometV0FeedDefs from "./types/sh/comet/v0/feed/defs.js"; 6 - export * as ShCometV0FeedGetActorPlaylists from "./types/sh/comet/v0/feed/getActorPlaylists.js"; 7 - export * as ShCometV0FeedGetActorTracks from "./types/sh/comet/v0/feed/getActorTracks.js"; 8 - export * as ShCometV0FeedLike from "./types/sh/comet/v0/feed/like.js"; 9 - export * as ShCometV0FeedPlay from "./types/sh/comet/v0/feed/play.js"; 10 - export * as ShCometV0FeedPlaylist from "./types/sh/comet/v0/feed/playlist.js"; 11 - export * as ShCometV0FeedPlaylistTrack from "./types/sh/comet/v0/feed/playlistTrack.js"; 12 - export * as ShCometV0FeedRepost from "./types/sh/comet/v0/feed/repost.js"; 13 - export * as ShCometV0FeedTrack from "./types/sh/comet/v0/feed/track.js"; 14 - export * as ShCometV0RichtextFacet from "./types/sh/comet/v0/richtext/facet.js";
-28
packages/lexicons/src/types/sh/comet/v0/actor/getProfile.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0ActorProfile from "./profile.js"; 5 - 6 - const _mainSchema = /*#__PURE__*/ v.query("sh.comet.v0.actor.getProfile", { 7 - params: /*#__PURE__*/ v.object({ 8 - actor: /*#__PURE__*/ v.actorIdentifierString(), 9 - }), 10 - output: { 11 - type: "lex", 12 - get schema() { 13 - return ShCometV0ActorProfile.viewSchema; 14 - }, 15 - }, 16 - }); 17 - 18 - type main$schematype = typeof _mainSchema; 19 - 20 - export interface mainSchema extends main$schematype {} 21 - 22 - export const mainSchema = _mainSchema as mainSchema; 23 - 24 - declare module "@atcute/lexicons/ambient" { 25 - interface XRPCQueries { 26 - "sh.comet.v0.actor.getProfile": mainSchema; 27 - } 28 - }
-33
packages/lexicons/src/types/sh/comet/v0/actor/getProfiles.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0ActorProfile from "./profile.js"; 5 - 6 - const _mainSchema = /*#__PURE__*/ v.query("sh.comet.v0.actor.getProfiles", { 7 - params: /*#__PURE__*/ v.object({ 8 - actors: /*#__PURE__*/ v.constrain( 9 - /*#__PURE__*/ v.array(/*#__PURE__*/ v.actorIdentifierString()), 10 - [/*#__PURE__*/ v.arrayLength(0, 25)], 11 - ), 12 - }), 13 - output: { 14 - type: "lex", 15 - schema: /*#__PURE__*/ v.object({ 16 - get profiles() { 17 - return /*#__PURE__*/ v.array(ShCometV0ActorProfile.viewSchema); 18 - }, 19 - }), 20 - }, 21 - }); 22 - 23 - type main$schematype = typeof _mainSchema; 24 - 25 - export interface mainSchema extends main$schematype {} 26 - 27 - export const mainSchema = _mainSchema as mainSchema; 28 - 29 - declare module "@atcute/lexicons/ambient" { 30 - interface XRPCQueries { 31 - "sh.comet.v0.actor.getProfiles": mainSchema; 32 - } 33 - }
-124
packages/lexicons/src/types/sh/comet/v0/actor/profile.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0RichtextFacet from "../richtext/facet.js"; 5 - 6 - const _mainSchema = /*#__PURE__*/ v.record( 7 - /*#__PURE__*/ v.literal("self"), 8 - /*#__PURE__*/ v.object({ 9 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.actor.profile"), 10 - avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 11 - banner: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 12 - createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 13 - description: /*#__PURE__*/ v.optional( 14 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 15 - /*#__PURE__*/ v.stringLength(0, 2560), 16 - /*#__PURE__*/ v.stringGraphemes(0, 256), 17 - ]), 18 - ), 19 - get descriptionFacets() { 20 - return /*#__PURE__*/ v.optional(ShCometV0RichtextFacet.mainSchema); 21 - }, 22 - displayName: /*#__PURE__*/ v.optional( 23 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 24 - /*#__PURE__*/ v.stringLength(0, 640), 25 - /*#__PURE__*/ v.stringGraphemes(0, 64), 26 - ]), 27 - ), 28 - featuredItems: /*#__PURE__*/ v.optional( 29 - /*#__PURE__*/ v.constrain( 30 - /*#__PURE__*/ v.array(/*#__PURE__*/ v.resourceUriString()), 31 - [/*#__PURE__*/ v.arrayLength(0, 5)], 32 - ), 33 - ), 34 - }), 35 - ); 36 - const _viewSchema = /*#__PURE__*/ v.object({ 37 - $type: /*#__PURE__*/ v.optional( 38 - /*#__PURE__*/ v.literal("sh.comet.v0.actor.profile#view"), 39 - ), 40 - avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 41 - createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 42 - did: /*#__PURE__*/ v.didString(), 43 - displayName: /*#__PURE__*/ v.optional( 44 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 45 - /*#__PURE__*/ v.stringLength(0, 640), 46 - /*#__PURE__*/ v.stringGraphemes(0, 64), 47 - ]), 48 - ), 49 - handle: /*#__PURE__*/ v.handleString(), 50 - indexedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 51 - get viewer() { 52 - return /*#__PURE__*/ v.optional(viewerStateSchema); 53 - }, 54 - }); 55 - const _viewFullSchema = /*#__PURE__*/ v.object({ 56 - $type: /*#__PURE__*/ v.optional( 57 - /*#__PURE__*/ v.literal("sh.comet.v0.actor.profile#viewFull"), 58 - ), 59 - avatar: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 60 - banner: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 61 - createdAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 62 - description: /*#__PURE__*/ v.optional( 63 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 64 - /*#__PURE__*/ v.stringLength(0, 2560), 65 - /*#__PURE__*/ v.stringGraphemes(0, 256), 66 - ]), 67 - ), 68 - get descriptionFacets() { 69 - return /*#__PURE__*/ v.optional(ShCometV0RichtextFacet.mainSchema); 70 - }, 71 - did: /*#__PURE__*/ v.didString(), 72 - displayName: /*#__PURE__*/ v.optional( 73 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 74 - /*#__PURE__*/ v.stringLength(0, 640), 75 - /*#__PURE__*/ v.stringGraphemes(0, 64), 76 - ]), 77 - ), 78 - featuredItems: /*#__PURE__*/ v.optional( 79 - /*#__PURE__*/ v.constrain( 80 - /*#__PURE__*/ v.array(/*#__PURE__*/ v.resourceUriString()), 81 - [/*#__PURE__*/ v.arrayLength(0, 5)], 82 - ), 83 - ), 84 - followersCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 85 - followsCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 86 - handle: /*#__PURE__*/ v.handleString(), 87 - indexedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 88 - playlistsCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 89 - tracksCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 90 - get viewer() { 91 - return /*#__PURE__*/ v.optional(viewerStateSchema); 92 - }, 93 - }); 94 - const _viewerStateSchema = /*#__PURE__*/ v.object({ 95 - $type: /*#__PURE__*/ v.optional( 96 - /*#__PURE__*/ v.literal("sh.comet.v0.actor.profile#viewerState"), 97 - ), 98 - }); 99 - 100 - type main$schematype = typeof _mainSchema; 101 - type view$schematype = typeof _viewSchema; 102 - type viewFull$schematype = typeof _viewFullSchema; 103 - type viewerState$schematype = typeof _viewerStateSchema; 104 - 105 - export interface mainSchema extends main$schematype {} 106 - export interface viewSchema extends view$schematype {} 107 - export interface viewFullSchema extends viewFull$schematype {} 108 - export interface viewerStateSchema extends viewerState$schematype {} 109 - 110 - export const mainSchema = _mainSchema as mainSchema; 111 - export const viewSchema = _viewSchema as viewSchema; 112 - export const viewFullSchema = _viewFullSchema as viewFullSchema; 113 - export const viewerStateSchema = _viewerStateSchema as viewerStateSchema; 114 - 115 - export interface Main extends v.InferInput<typeof mainSchema> {} 116 - export interface View extends v.InferInput<typeof viewSchema> {} 117 - export interface ViewFull extends v.InferInput<typeof viewFullSchema> {} 118 - export interface ViewerState extends v.InferInput<typeof viewerStateSchema> {} 119 - 120 - declare module "@atcute/lexicons/ambient" { 121 - interface Records { 122 - "sh.comet.v0.actor.profile": mainSchema; 123 - } 124 - }
-43
packages/lexicons/src/types/sh/comet/v0/feed/comment.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0RichtextFacet from "../richtext/facet.js"; 5 - 6 - const _mainSchema = /*#__PURE__*/ v.record( 7 - /*#__PURE__*/ v.tidString(), 8 - /*#__PURE__*/ v.object({ 9 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.feed.comment"), 10 - createdAt: /*#__PURE__*/ v.datetimeString(), 11 - get facets() { 12 - return /*#__PURE__*/ v.optional( 13 - /*#__PURE__*/ v.array(ShCometV0RichtextFacet.mainSchema), 14 - ); 15 - }, 16 - langs: /*#__PURE__*/ v.optional( 17 - /*#__PURE__*/ v.constrain( 18 - /*#__PURE__*/ v.array(/*#__PURE__*/ v.languageCodeString()), 19 - [/*#__PURE__*/ v.arrayLength(0, 3)], 20 - ), 21 - ), 22 - reply: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 23 - subject: /*#__PURE__*/ v.resourceUriString(), 24 - text: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 25 - /*#__PURE__*/ v.stringLength(1, 3000), 26 - /*#__PURE__*/ v.stringGraphemes(1, 300), 27 - ]), 28 - }), 29 - ); 30 - 31 - type main$schematype = typeof _mainSchema; 32 - 33 - export interface mainSchema extends main$schematype {} 34 - 35 - export const mainSchema = _mainSchema as mainSchema; 36 - 37 - export interface Main extends v.InferInput<typeof mainSchema> {} 38 - 39 - declare module "@atcute/lexicons/ambient" { 40 - interface Records { 41 - "sh.comet.v0.feed.comment": mainSchema; 42 - } 43 - }
-46
packages/lexicons/src/types/sh/comet/v0/feed/defs.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - 4 - const _buyLinkSchema = /*#__PURE__*/ v.literal("sh.comet.v0.feed.defs#buyLink"); 5 - const _downloadLinkSchema = /*#__PURE__*/ v.literal( 6 - "sh.comet.v0.feed.defs#downloadLink", 7 - ); 8 - const _linkSchema = /*#__PURE__*/ v.object({ 9 - $type: /*#__PURE__*/ v.optional( 10 - /*#__PURE__*/ v.literal("sh.comet.v0.feed.defs#link"), 11 - ), 12 - type: /*#__PURE__*/ v.string< 13 - | "sh.comet.v0.feed.defs#buyLink" 14 - | "sh.comet.v0.feed.defs#downloadLink" 15 - | (string & {}) 16 - >(), 17 - value: /*#__PURE__*/ v.genericUriString(), 18 - }); 19 - const _viewerStateSchema = /*#__PURE__*/ v.object({ 20 - $type: /*#__PURE__*/ v.optional( 21 - /*#__PURE__*/ v.literal("sh.comet.v0.feed.defs#viewerState"), 22 - ), 23 - featured: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 24 - like: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 25 - repost: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 26 - }); 27 - 28 - type buyLink$schematype = typeof _buyLinkSchema; 29 - type downloadLink$schematype = typeof _downloadLinkSchema; 30 - type link$schematype = typeof _linkSchema; 31 - type viewerState$schematype = typeof _viewerStateSchema; 32 - 33 - export interface buyLinkSchema extends buyLink$schematype {} 34 - export interface downloadLinkSchema extends downloadLink$schematype {} 35 - export interface linkSchema extends link$schematype {} 36 - export interface viewerStateSchema extends viewerState$schematype {} 37 - 38 - export const buyLinkSchema = _buyLinkSchema as buyLinkSchema; 39 - export const downloadLinkSchema = _downloadLinkSchema as downloadLinkSchema; 40 - export const linkSchema = _linkSchema as linkSchema; 41 - export const viewerStateSchema = _viewerStateSchema as viewerStateSchema; 42 - 43 - export type BuyLink = v.InferInput<typeof buyLinkSchema>; 44 - export type DownloadLink = v.InferInput<typeof downloadLinkSchema>; 45 - export interface Link extends v.InferInput<typeof linkSchema> {} 46 - export interface ViewerState extends v.InferInput<typeof viewerStateSchema> {}
-41
packages/lexicons/src/types/sh/comet/v0/feed/getActorPlaylists.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0FeedPlaylist from "./playlist.js"; 5 - 6 - const _mainSchema = /*#__PURE__*/ v.query( 7 - "sh.comet.v0.feed.getActorPlaylists", 8 - { 9 - params: /*#__PURE__*/ v.object({ 10 - actor: /*#__PURE__*/ v.actorIdentifierString(), 11 - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 12 - limit: /*#__PURE__*/ v.optional( 13 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ 14 - /*#__PURE__*/ v.integerRange(1, 100), 15 - ]), 16 - 50, 17 - ), 18 - }), 19 - output: { 20 - type: "lex", 21 - schema: /*#__PURE__*/ v.object({ 22 - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 23 - get playlists() { 24 - return /*#__PURE__*/ v.array(ShCometV0FeedPlaylist.viewSchema); 25 - }, 26 - }), 27 - }, 28 - }, 29 - ); 30 - 31 - type main$schematype = typeof _mainSchema; 32 - 33 - export interface mainSchema extends main$schematype {} 34 - 35 - export const mainSchema = _mainSchema as mainSchema; 36 - 37 - declare module "@atcute/lexicons/ambient" { 38 - interface XRPCQueries { 39 - "sh.comet.v0.feed.getActorPlaylists": mainSchema; 40 - } 41 - }
-38
packages/lexicons/src/types/sh/comet/v0/feed/getActorTracks.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0FeedTrack from "./track.js"; 5 - 6 - const _mainSchema = /*#__PURE__*/ v.query("sh.comet.v0.feed.getActorTracks", { 7 - params: /*#__PURE__*/ v.object({ 8 - actor: /*#__PURE__*/ v.actorIdentifierString(), 9 - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 10 - limit: /*#__PURE__*/ v.optional( 11 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ 12 - /*#__PURE__*/ v.integerRange(1, 100), 13 - ]), 14 - 50, 15 - ), 16 - }), 17 - output: { 18 - type: "lex", 19 - schema: /*#__PURE__*/ v.object({ 20 - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 21 - get tracks() { 22 - return /*#__PURE__*/ v.array(ShCometV0FeedTrack.viewSchema); 23 - }, 24 - }), 25 - }, 26 - }); 27 - 28 - type main$schematype = typeof _mainSchema; 29 - 30 - export interface mainSchema extends main$schematype {} 31 - 32 - export const mainSchema = _mainSchema as mainSchema; 33 - 34 - declare module "@atcute/lexicons/ambient" { 35 - interface XRPCQueries { 36 - "sh.comet.v0.feed.getActorTracks": mainSchema; 37 - } 38 - }
-26
packages/lexicons/src/types/sh/comet/v0/feed/like.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - 5 - const _mainSchema = /*#__PURE__*/ v.record( 6 - /*#__PURE__*/ v.tidString(), 7 - /*#__PURE__*/ v.object({ 8 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.feed.like"), 9 - createdAt: /*#__PURE__*/ v.datetimeString(), 10 - subject: /*#__PURE__*/ v.resourceUriString(), 11 - }), 12 - ); 13 - 14 - type main$schematype = typeof _mainSchema; 15 - 16 - export interface mainSchema extends main$schematype {} 17 - 18 - export const mainSchema = _mainSchema as mainSchema; 19 - 20 - export interface Main extends v.InferInput<typeof mainSchema> {} 21 - 22 - declare module "@atcute/lexicons/ambient" { 23 - interface Records { 24 - "sh.comet.v0.feed.like": mainSchema; 25 - } 26 - }
-26
packages/lexicons/src/types/sh/comet/v0/feed/play.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - 5 - const _mainSchema = /*#__PURE__*/ v.record( 6 - /*#__PURE__*/ v.tidString(), 7 - /*#__PURE__*/ v.object({ 8 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.feed.play"), 9 - createdAt: /*#__PURE__*/ v.datetimeString(), 10 - subject: /*#__PURE__*/ v.resourceUriString(), 11 - }), 12 - ); 13 - 14 - type main$schematype = typeof _mainSchema; 15 - 16 - export interface mainSchema extends main$schematype {} 17 - 18 - export const mainSchema = _mainSchema as mainSchema; 19 - 20 - export interface Main extends v.InferInput<typeof mainSchema> {} 21 - 22 - declare module "@atcute/lexicons/ambient" { 23 - interface Records { 24 - "sh.comet.v0.feed.play": mainSchema; 25 - } 26 - }
-119
packages/lexicons/src/types/sh/comet/v0/feed/playlist.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0ActorProfile from "../actor/profile.js"; 5 - import * as ShCometV0FeedDefs from "./defs.js"; 6 - import * as ShCometV0FeedTrack from "./track.js"; 7 - import * as ShCometV0RichtextFacet from "../richtext/facet.js"; 8 - 9 - const _albumSchema = /*#__PURE__*/ v.literal("sh.comet.v0.feed.playlist#album"); 10 - const _compilationSchema = /*#__PURE__*/ v.literal( 11 - "sh.comet.v0.feed.playlist#compilation", 12 - ); 13 - const _mainSchema = /*#__PURE__*/ v.record( 14 - /*#__PURE__*/ v.tidString(), 15 - /*#__PURE__*/ v.object({ 16 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.feed.playlist"), 17 - createdAt: /*#__PURE__*/ v.datetimeString(), 18 - description: /*#__PURE__*/ v.optional( 19 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 20 - /*#__PURE__*/ v.stringLength(0, 20000), 21 - /*#__PURE__*/ v.stringGraphemes(0, 2000), 22 - ]), 23 - ), 24 - get descriptionFacets() { 25 - return /*#__PURE__*/ v.optional(ShCometV0RichtextFacet.mainSchema); 26 - }, 27 - image: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 28 - get link() { 29 - return /*#__PURE__*/ v.optional(ShCometV0FeedDefs.linkSchema); 30 - }, 31 - tags: /*#__PURE__*/ v.optional( 32 - /*#__PURE__*/ v.constrain( 33 - /*#__PURE__*/ v.array( 34 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 35 - /*#__PURE__*/ v.stringLength(0, 640), 36 - /*#__PURE__*/ v.stringGraphemes(0, 64), 37 - ]), 38 - ), 39 - [/*#__PURE__*/ v.arrayLength(0, 8)], 40 - ), 41 - ), 42 - title: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 43 - /*#__PURE__*/ v.stringLength(1, 2560), 44 - /*#__PURE__*/ v.stringGraphemes(0, 256), 45 - ]), 46 - type: /*#__PURE__*/ v.string< 47 - | "sh.comet.v0.feed.playlist#album" 48 - | "sh.comet.v0.feed.playlist#compilation" 49 - | "sh.comet.v0.feed.playlist#playlist" 50 - | "sh.comet.v0.feed.playlist#podcast" 51 - | (string & {}) 52 - >(), 53 - }), 54 - ); 55 - const _playlistSchema = /*#__PURE__*/ v.literal( 56 - "sh.comet.v0.feed.playlist#playlist", 57 - ); 58 - const _podcastSchema = /*#__PURE__*/ v.literal( 59 - "sh.comet.v0.feed.playlist#podcast", 60 - ); 61 - const _viewSchema = /*#__PURE__*/ v.object({ 62 - $type: /*#__PURE__*/ v.optional( 63 - /*#__PURE__*/ v.literal("sh.comet.v0.feed.playlist#view"), 64 - ), 65 - get author() { 66 - return ShCometV0ActorProfile.viewFullSchema; 67 - }, 68 - cid: /*#__PURE__*/ v.cidString(), 69 - commentCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 70 - image: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 71 - indexedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), 72 - likeCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 73 - get record() { 74 - return mainSchema; 75 - }, 76 - repostCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 77 - trackCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 78 - get tracks() { 79 - return /*#__PURE__*/ v.array(ShCometV0FeedTrack.viewSchema); 80 - }, 81 - uri: /*#__PURE__*/ v.resourceUriString(), 82 - get viewer() { 83 - return /*#__PURE__*/ v.optional(ShCometV0FeedDefs.viewerStateSchema); 84 - }, 85 - }); 86 - 87 - type album$schematype = typeof _albumSchema; 88 - type compilation$schematype = typeof _compilationSchema; 89 - type main$schematype = typeof _mainSchema; 90 - type playlist$schematype = typeof _playlistSchema; 91 - type podcast$schematype = typeof _podcastSchema; 92 - type view$schematype = typeof _viewSchema; 93 - 94 - export interface albumSchema extends album$schematype {} 95 - export interface compilationSchema extends compilation$schematype {} 96 - export interface mainSchema extends main$schematype {} 97 - export interface playlistSchema extends playlist$schematype {} 98 - export interface podcastSchema extends podcast$schematype {} 99 - export interface viewSchema extends view$schematype {} 100 - 101 - export const albumSchema = _albumSchema as albumSchema; 102 - export const compilationSchema = _compilationSchema as compilationSchema; 103 - export const mainSchema = _mainSchema as mainSchema; 104 - export const playlistSchema = _playlistSchema as playlistSchema; 105 - export const podcastSchema = _podcastSchema as podcastSchema; 106 - export const viewSchema = _viewSchema as viewSchema; 107 - 108 - export type Album = v.InferInput<typeof albumSchema>; 109 - export type Compilation = v.InferInput<typeof compilationSchema>; 110 - export interface Main extends v.InferInput<typeof mainSchema> {} 111 - export type Playlist = v.InferInput<typeof playlistSchema>; 112 - export type Podcast = v.InferInput<typeof podcastSchema>; 113 - export interface View extends v.InferInput<typeof viewSchema> {} 114 - 115 - declare module "@atcute/lexicons/ambient" { 116 - interface Records { 117 - "sh.comet.v0.feed.playlist": mainSchema; 118 - } 119 - }
-27
packages/lexicons/src/types/sh/comet/v0/feed/playlistTrack.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - 5 - const _mainSchema = /*#__PURE__*/ v.record( 6 - /*#__PURE__*/ v.tidString(), 7 - /*#__PURE__*/ v.object({ 8 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.feed.playlistTrack"), 9 - playlist: /*#__PURE__*/ v.resourceUriString(), 10 - position: /*#__PURE__*/ v.integer(), 11 - track: /*#__PURE__*/ v.resourceUriString(), 12 - }), 13 - ); 14 - 15 - type main$schematype = typeof _mainSchema; 16 - 17 - export interface mainSchema extends main$schematype {} 18 - 19 - export const mainSchema = _mainSchema as mainSchema; 20 - 21 - export interface Main extends v.InferInput<typeof mainSchema> {} 22 - 23 - declare module "@atcute/lexicons/ambient" { 24 - interface Records { 25 - "sh.comet.v0.feed.playlistTrack": mainSchema; 26 - } 27 - }
-26
packages/lexicons/src/types/sh/comet/v0/feed/repost.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - 5 - const _mainSchema = /*#__PURE__*/ v.record( 6 - /*#__PURE__*/ v.tidString(), 7 - /*#__PURE__*/ v.object({ 8 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.feed.repost"), 9 - createdAt: /*#__PURE__*/ v.datetimeString(), 10 - subject: /*#__PURE__*/ v.resourceUriString(), 11 - }), 12 - ); 13 - 14 - type main$schematype = typeof _mainSchema; 15 - 16 - export interface mainSchema extends main$schematype {} 17 - 18 - export const mainSchema = _mainSchema as mainSchema; 19 - 20 - export interface Main extends v.InferInput<typeof mainSchema> {} 21 - 22 - declare module "@atcute/lexicons/ambient" { 23 - interface Records { 24 - "sh.comet.v0.feed.repost": mainSchema; 25 - } 26 - }
-84
packages/lexicons/src/types/sh/comet/v0/feed/track.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - import type {} from "@atcute/lexicons/ambient"; 4 - import * as ShCometV0ActorProfile from "../actor/profile.js"; 5 - import * as ShCometV0FeedDefs from "./defs.js"; 6 - import * as ShCometV0RichtextFacet from "../richtext/facet.js"; 7 - 8 - const _mainSchema = /*#__PURE__*/ v.record( 9 - /*#__PURE__*/ v.tidString(), 10 - /*#__PURE__*/ v.object({ 11 - $type: /*#__PURE__*/ v.literal("sh.comet.v0.feed.track"), 12 - audio: /*#__PURE__*/ v.blob(), 13 - createdAt: /*#__PURE__*/ v.datetimeString(), 14 - description: /*#__PURE__*/ v.optional( 15 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 16 - /*#__PURE__*/ v.stringLength(0, 20000), 17 - /*#__PURE__*/ v.stringGraphemes(0, 2000), 18 - ]), 19 - ), 20 - get descriptionFacets() { 21 - return /*#__PURE__*/ v.optional(ShCometV0RichtextFacet.mainSchema); 22 - }, 23 - image: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), 24 - get link() { 25 - return /*#__PURE__*/ v.optional(ShCometV0FeedDefs.linkSchema); 26 - }, 27 - tags: /*#__PURE__*/ v.optional( 28 - /*#__PURE__*/ v.constrain( 29 - /*#__PURE__*/ v.array( 30 - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 31 - /*#__PURE__*/ v.stringLength(0, 640), 32 - /*#__PURE__*/ v.stringGraphemes(0, 64), 33 - ]), 34 - ), 35 - [/*#__PURE__*/ v.arrayLength(0, 8)], 36 - ), 37 - ), 38 - title: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 39 - /*#__PURE__*/ v.stringLength(1, 2560), 40 - /*#__PURE__*/ v.stringGraphemes(0, 256), 41 - ]), 42 - }), 43 - ); 44 - const _viewSchema = /*#__PURE__*/ v.object({ 45 - $type: /*#__PURE__*/ v.optional( 46 - /*#__PURE__*/ v.literal("sh.comet.v0.feed.track#view"), 47 - ), 48 - audio: /*#__PURE__*/ v.genericUriString(), 49 - get author() { 50 - return ShCometV0ActorProfile.viewFullSchema; 51 - }, 52 - cid: /*#__PURE__*/ v.cidString(), 53 - commentCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 54 - image: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 55 - indexedAt: /*#__PURE__*/ v.datetimeString(), 56 - likeCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 57 - playCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 58 - get record() { 59 - return mainSchema; 60 - }, 61 - repostCount: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 62 - uri: /*#__PURE__*/ v.resourceUriString(), 63 - get viewer() { 64 - return /*#__PURE__*/ v.optional(ShCometV0FeedDefs.viewerStateSchema); 65 - }, 66 - }); 67 - 68 - type main$schematype = typeof _mainSchema; 69 - type view$schematype = typeof _viewSchema; 70 - 71 - export interface mainSchema extends main$schematype {} 72 - export interface viewSchema extends view$schematype {} 73 - 74 - export const mainSchema = _mainSchema as mainSchema; 75 - export const viewSchema = _viewSchema as viewSchema; 76 - 77 - export interface Main extends v.InferInput<typeof mainSchema> {} 78 - export interface View extends v.InferInput<typeof viewSchema> {} 79 - 80 - declare module "@atcute/lexicons/ambient" { 81 - interface Records { 82 - "sh.comet.v0.feed.track": mainSchema; 83 - } 84 - }
-78
packages/lexicons/src/types/sh/comet/v0/richtext/facet.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - 4 - const _byteSliceSchema = /*#__PURE__*/ v.object({ 5 - $type: /*#__PURE__*/ v.optional( 6 - /*#__PURE__*/ v.literal("sh.comet.v0.richtext.facet#byteSlice"), 7 - ), 8 - byteEnd: /*#__PURE__*/ v.integer(), 9 - byteStart: /*#__PURE__*/ v.integer(), 10 - }); 11 - const _linkSchema = /*#__PURE__*/ v.object({ 12 - $type: /*#__PURE__*/ v.optional( 13 - /*#__PURE__*/ v.literal("sh.comet.v0.richtext.facet#link"), 14 - ), 15 - uri: /*#__PURE__*/ v.genericUriString(), 16 - }); 17 - const _mainSchema = /*#__PURE__*/ v.object({ 18 - $type: /*#__PURE__*/ v.optional( 19 - /*#__PURE__*/ v.literal("sh.comet.v0.richtext.facet"), 20 - ), 21 - get features() { 22 - return /*#__PURE__*/ v.array( 23 - /*#__PURE__*/ v.variant([linkSchema, mentionSchema, tagSchema]), 24 - ); 25 - }, 26 - get index() { 27 - return byteSliceSchema; 28 - }, 29 - }); 30 - const _mentionSchema = /*#__PURE__*/ v.object({ 31 - $type: /*#__PURE__*/ v.optional( 32 - /*#__PURE__*/ v.literal("sh.comet.v0.richtext.facet#mention"), 33 - ), 34 - did: /*#__PURE__*/ v.didString(), 35 - }); 36 - const _tagSchema = /*#__PURE__*/ v.object({ 37 - $type: /*#__PURE__*/ v.optional( 38 - /*#__PURE__*/ v.literal("sh.comet.v0.richtext.facet#tag"), 39 - ), 40 - tag: /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 41 - /*#__PURE__*/ v.stringLength(0, 640), 42 - /*#__PURE__*/ v.stringGraphemes(0, 64), 43 - ]), 44 - }); 45 - const _timestampSchema = /*#__PURE__*/ v.object({ 46 - $type: /*#__PURE__*/ v.optional( 47 - /*#__PURE__*/ v.literal("sh.comet.v0.richtext.facet#timestamp"), 48 - ), 49 - timestamp: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), 50 - }); 51 - 52 - type byteSlice$schematype = typeof _byteSliceSchema; 53 - type link$schematype = typeof _linkSchema; 54 - type main$schematype = typeof _mainSchema; 55 - type mention$schematype = typeof _mentionSchema; 56 - type tag$schematype = typeof _tagSchema; 57 - type timestamp$schematype = typeof _timestampSchema; 58 - 59 - export interface byteSliceSchema extends byteSlice$schematype {} 60 - export interface linkSchema extends link$schematype {} 61 - export interface mainSchema extends main$schematype {} 62 - export interface mentionSchema extends mention$schematype {} 63 - export interface tagSchema extends tag$schematype {} 64 - export interface timestampSchema extends timestamp$schematype {} 65 - 66 - export const byteSliceSchema = _byteSliceSchema as byteSliceSchema; 67 - export const linkSchema = _linkSchema as linkSchema; 68 - export const mainSchema = _mainSchema as mainSchema; 69 - export const mentionSchema = _mentionSchema as mentionSchema; 70 - export const tagSchema = _tagSchema as tagSchema; 71 - export const timestampSchema = _timestampSchema as timestampSchema; 72 - 73 - export interface ByteSlice extends v.InferInput<typeof byteSliceSchema> {} 74 - export interface Link extends v.InferInput<typeof linkSchema> {} 75 - export interface Main extends v.InferInput<typeof mainSchema> {} 76 - export interface Mention extends v.InferInput<typeof mentionSchema> {} 77 - export interface Tag extends v.InferInput<typeof tagSchema> {} 78 - export interface Timestamp extends v.InferInput<typeof timestampSchema> {}
-28
packages/lexicons/tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - // Environment setup & latest features 4 - "lib": ["ESNext"], 5 - "target": "ESNext", 6 - "module": "ESNext", 7 - "moduleDetection": "force", 8 - "jsx": "react-jsx", 9 - "allowJs": true, 10 - 11 - // Bundler mode 12 - "moduleResolution": "bundler", 13 - "allowImportingTsExtensions": true, 14 - "verbatimModuleSyntax": true, 15 - "noEmit": true, 16 - 17 - // Best practices 18 - "strict": true, 19 - "skipLibCheck": true, 20 - "noFallthroughCasesInSwitch": true, 21 - "noUncheckedIndexedAccess": true, 22 - 23 - // Some stricter flags (disabled by default) 24 - "noUnusedLocals": false, 25 - "noUnusedParameters": false, 26 - "noPropertyAccessFromIndexSignature": false 27 - } 28 - }
-36
packages/test/.gitignore
··· 1 - # dependencies (bun install) 2 - node_modules 3 - 4 - # output 5 - out 6 - dist 7 - *.tgz 8 - 9 - # code coverage 10 - coverage 11 - *.lcov 12 - 13 - # logs 14 - logs 15 - _.log 16 - report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 - 18 - # dotenv environment variable files 19 - .env 20 - .env.development.local 21 - .env.test.local 22 - .env.production.local 23 - .env.local 24 - 25 - # caches 26 - .eslintcache 27 - .cache 28 - *.tsbuildinfo 29 - 30 - # IntelliJ based IDEs 31 - .idea 32 - 33 - # Finder (MacOS) folder config 34 - .DS_Store 35 - 36 - *.opus
-15
packages/test/README.md
··· 1 - # test 2 - 3 - To install dependencies: 4 - 5 - ```bash 6 - bun install 7 - ``` 8 - 9 - To run: 10 - 11 - ```bash 12 - bun run index.ts 13 - ``` 14 - 15 - This project was created using `bun init` in bun v1.2.8. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
-171
packages/test/index.ts
··· 1 - import { 2 - Client, 3 - CredentialManager, 4 - ok, 5 - simpleFetchHandler, 6 - } from "@atcute/client"; 7 - 8 - import type {} from "@atcute/bluesky"; 9 - import type { ComAtprotoRepoApplyWrites } from "@atcute/atproto"; 10 - import { 11 - ShCometV0FeedPlaylist, 12 - ShCometV0FeedPlaylistTrack, 13 - ShCometV0FeedTrack, 14 - } from "@comet/lexicons"; 15 - import type { ResourceUri } from "@atcute/lexicons"; 16 - import { splitEvery } from "rambdax"; 17 - 18 - // const manager = new CredentialManager({ service: "https://pds.ovy.sh" }); 19 - const manager = new CredentialManager({ service: "https://bsky.social" }); 20 - const rpc = new Client({ handler: manager }); 21 - 22 - interface Type { 23 - $type: `${string}.${string}.${string}`; 24 - [key: string]: any; 25 - } 26 - 27 - const createRecord = <T extends Type>(record: T) => 28 - ok( 29 - rpc.post("com.atproto.repo.createRecord", { 30 - input: { collection: record.$type, repo: manager.session!.did, record }, 31 - }), 32 - ); 33 - 34 - await manager.login({ 35 - identifier: Bun.env.COMET_TEST_IDENT!, 36 - password: Bun.env.COMET_TEST_PASSWORD!, 37 - }); 38 - 39 - /** Upload a test audio blob. */ 40 - const uploadAudio = async () => { 41 - const inputAudio = Bun.file("./test-track.opus"); 42 - const { blob: audio } = await ok( 43 - rpc.post("com.atproto.repo.uploadBlob", { input: inputAudio }), 44 - ); 45 - console.log(audio); 46 - }; 47 - 48 - /** Create a test track record. */ 49 - const createTrack = async () => { 50 - const audio = { 51 - $type: "blob", 52 - ref: { 53 - $link: "bafkreifiu63dr52dxzrurnspha5xvzlzqkho3hdzdhu6zvthrrvdpd6yve", 54 - }, 55 - mimeType: "audio/opus", 56 - size: 3349806, 57 - } as const; 58 - 59 - const track: ShCometV0FeedTrack.Main = { 60 - $type: "sh.comet.v0.feed.track", 61 - audio, 62 - title: "Testing Track 6", 63 - createdAt: new Date().toJSON(), 64 - }; 65 - 66 - const response = await createRecord(track); 67 - console.log(response); 68 - }; 69 - 70 - /** Create a test playlist */ 71 - const createPlaylist = async () => { 72 - const playlistRecord: ShCometV0FeedPlaylist.Main = { 73 - $type: "sh.comet.v0.feed.playlist", 74 - title: "Testing Playlist", 75 - type: "sh.comet.v0.feed.playlist#playlist", 76 - createdAt: new Date().toJSON(), 77 - tags: ["testing", "music"], 78 - }; 79 - 80 - const { uri: playlist } = await createRecord(playlistRecord); 81 - console.log("created playlist", playlist); 82 - 83 - const collection = "sh.comet.v0.feed.playlistTrack"; 84 - const tracks = [ 85 - "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2gsib2s2e", 86 - "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2muqtnu2w", 87 - "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2njjm6p2y", 88 - "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2nrehj52o", 89 - "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2nnacyg23", 90 - ] as ResourceUri[]; 91 - 92 - const created = await ok( 93 - rpc.post("com.atproto.repo.applyWrites", { 94 - input: { 95 - repo: manager.session!.did, 96 - writes: tracks.map( 97 - (track, position) => 98 - ({ 99 - $type: "com.atproto.repo.applyWrites#create", 100 - collection, 101 - value: { 102 - $type: collection, 103 - playlist, 104 - track, 105 - position, 106 - } satisfies ShCometV0FeedPlaylistTrack.Main, 107 - }) satisfies ComAtprotoRepoApplyWrites.Create, 108 - ), 109 - }, 110 - }), 111 - ); 112 - 113 - console.log(created); 114 - console.log("created playlist tracks"); 115 - }; 116 - 117 - /** Create a veeeeery large test playlist. */ 118 - const createLargePlaylist = async () => { 119 - const playlistRecord: ShCometV0FeedPlaylist.Main = { 120 - $type: "sh.comet.v0.feed.playlist", 121 - title: "Very lorge playlist", 122 - type: "sh.comet.v0.feed.playlist#compilation", 123 - createdAt: new Date().toJSON(), 124 - }; 125 - 126 - const { uri: playlist } = await createRecord(playlistRecord); 127 - console.log("created playlist", playlist); 128 - 129 - const collection = "sh.comet.v0.feed.playlistTrack"; 130 - const tracks = new Array(2500) 131 - .fill( 132 - "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2gsib2s2e" as ResourceUri, 133 - ) 134 - .map( 135 - (track, position) => 136 - ({ 137 - $type: "com.atproto.repo.applyWrites#create", 138 - collection, 139 - value: { 140 - $type: collection, 141 - playlist, 142 - track, 143 - position, 144 - } satisfies ShCometV0FeedPlaylistTrack.Main, 145 - }) satisfies ComAtprotoRepoApplyWrites.Create, 146 - ); 147 - 148 - for (const chunk of splitEvery(100, tracks)) { 149 - // TODO: don't hit ratelimit 150 - await ok( 151 - rpc.post("com.atproto.repo.applyWrites", { 152 - input: { 153 - repo: manager.session!.did, 154 - writes: chunk, 155 - }, 156 - }), 157 - ); 158 - console.log("wrote chunk"); 159 - } 160 - 161 - console.log("created playlist tracks"); 162 - }; 163 - 164 - // const testQuery = async () => { 165 - // const x = await ok(rpc.get("sh.comet.v0.actor.getProfile", {})); 166 - // }; 167 - 168 - // await uploadAudio(); 169 - // await createTrack(); 170 - // await createPlaylist(); 171 - await createLargePlaylist();
-19
packages/test/package.json
··· 1 - { 2 - "name": "test", 3 - "module": "index.ts", 4 - "type": "module", 5 - "private": true, 6 - "devDependencies": { 7 - "@types/bun": "latest" 8 - }, 9 - "peerDependencies": { 10 - "typescript": "^5" 11 - }, 12 - "dependencies": { 13 - "@atcute/atproto": "^3.0.2", 14 - "@atcute/bluesky": "^3.0.2", 15 - "@atcute/client": "^4.0.2", 16 - "@comet/lexicons": "workspace:*", 17 - "rambdax": "^11.3.1" 18 - } 19 - }
-28
packages/test/tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - // Environment setup & latest features 4 - "lib": ["ESNext"], 5 - "target": "ESNext", 6 - "module": "ESNext", 7 - "moduleDetection": "force", 8 - "jsx": "react-jsx", 9 - "allowJs": true, 10 - 11 - // Bundler mode 12 - "moduleResolution": "bundler", 13 - "allowImportingTsExtensions": true, 14 - "verbatimModuleSyntax": true, 15 - "noEmit": true, 16 - 17 - // Best practices 18 - "strict": true, 19 - "skipLibCheck": true, 20 - "noFallthroughCasesInSwitch": true, 21 - "noUncheckedIndexedAccess": true, 22 - 23 - // Some stricter flags (disabled by default) 24 - "noUnusedLocals": false, 25 - "noUnusedParameters": false, 26 - "noPropertyAccessFromIndexSignature": false 27 - } 28 - }
+705
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + '@fontsource-variable/work-sans': 12 + specifier: ^5.2.8 13 + version: 5.2.8 14 + devDependencies: 15 + '@tailwindcss/cli': 16 + specifier: ^4.1.17 17 + version: 4.1.17 18 + prettier: 19 + specifier: ^3.5.3 20 + version: 3.7.1 21 + prettier-plugin-tailwindcss: 22 + specifier: ^0.6.11 23 + version: 0.6.14(prettier@3.7.1) 24 + tailwindcss: 25 + specifier: ^4.1.17 26 + version: 4.1.17 27 + typescript: 28 + specifier: ^5 29 + version: 5.9.3 30 + 31 + packages: 32 + 33 + '@fontsource-variable/work-sans@5.2.8': 34 + resolution: {integrity: sha512-8uWtTt0/B5NxGie9xUVD5y5Ch4Q+Hy7kFYKtUpwYbzSAgJEoaMxT8rMnfnK7zfAYSLC8GnGO1/tXrFtKIYYQVQ==} 35 + 36 + '@jridgewell/gen-mapping@0.3.13': 37 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 38 + 39 + '@jridgewell/remapping@2.3.5': 40 + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} 41 + 42 + '@jridgewell/resolve-uri@3.1.2': 43 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 44 + engines: {node: '>=6.0.0'} 45 + 46 + '@jridgewell/sourcemap-codec@1.5.5': 47 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 48 + 49 + '@jridgewell/trace-mapping@0.3.31': 50 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 51 + 52 + '@parcel/watcher-android-arm64@2.5.1': 53 + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} 54 + engines: {node: '>= 10.0.0'} 55 + cpu: [arm64] 56 + os: [android] 57 + 58 + '@parcel/watcher-darwin-arm64@2.5.1': 59 + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} 60 + engines: {node: '>= 10.0.0'} 61 + cpu: [arm64] 62 + os: [darwin] 63 + 64 + '@parcel/watcher-darwin-x64@2.5.1': 65 + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} 66 + engines: {node: '>= 10.0.0'} 67 + cpu: [x64] 68 + os: [darwin] 69 + 70 + '@parcel/watcher-freebsd-x64@2.5.1': 71 + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} 72 + engines: {node: '>= 10.0.0'} 73 + cpu: [x64] 74 + os: [freebsd] 75 + 76 + '@parcel/watcher-linux-arm-glibc@2.5.1': 77 + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} 78 + engines: {node: '>= 10.0.0'} 79 + cpu: [arm] 80 + os: [linux] 81 + 82 + '@parcel/watcher-linux-arm-musl@2.5.1': 83 + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} 84 + engines: {node: '>= 10.0.0'} 85 + cpu: [arm] 86 + os: [linux] 87 + 88 + '@parcel/watcher-linux-arm64-glibc@2.5.1': 89 + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} 90 + engines: {node: '>= 10.0.0'} 91 + cpu: [arm64] 92 + os: [linux] 93 + 94 + '@parcel/watcher-linux-arm64-musl@2.5.1': 95 + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} 96 + engines: {node: '>= 10.0.0'} 97 + cpu: [arm64] 98 + os: [linux] 99 + 100 + '@parcel/watcher-linux-x64-glibc@2.5.1': 101 + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} 102 + engines: {node: '>= 10.0.0'} 103 + cpu: [x64] 104 + os: [linux] 105 + 106 + '@parcel/watcher-linux-x64-musl@2.5.1': 107 + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} 108 + engines: {node: '>= 10.0.0'} 109 + cpu: [x64] 110 + os: [linux] 111 + 112 + '@parcel/watcher-win32-arm64@2.5.1': 113 + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} 114 + engines: {node: '>= 10.0.0'} 115 + cpu: [arm64] 116 + os: [win32] 117 + 118 + '@parcel/watcher-win32-ia32@2.5.1': 119 + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} 120 + engines: {node: '>= 10.0.0'} 121 + cpu: [ia32] 122 + os: [win32] 123 + 124 + '@parcel/watcher-win32-x64@2.5.1': 125 + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} 126 + engines: {node: '>= 10.0.0'} 127 + cpu: [x64] 128 + os: [win32] 129 + 130 + '@parcel/watcher@2.5.1': 131 + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} 132 + engines: {node: '>= 10.0.0'} 133 + 134 + '@tailwindcss/cli@4.1.17': 135 + resolution: {integrity: sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg==} 136 + hasBin: true 137 + 138 + '@tailwindcss/node@4.1.17': 139 + resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} 140 + 141 + '@tailwindcss/oxide-android-arm64@4.1.17': 142 + resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} 143 + engines: {node: '>= 10'} 144 + cpu: [arm64] 145 + os: [android] 146 + 147 + '@tailwindcss/oxide-darwin-arm64@4.1.17': 148 + resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} 149 + engines: {node: '>= 10'} 150 + cpu: [arm64] 151 + os: [darwin] 152 + 153 + '@tailwindcss/oxide-darwin-x64@4.1.17': 154 + resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} 155 + engines: {node: '>= 10'} 156 + cpu: [x64] 157 + os: [darwin] 158 + 159 + '@tailwindcss/oxide-freebsd-x64@4.1.17': 160 + resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} 161 + engines: {node: '>= 10'} 162 + cpu: [x64] 163 + os: [freebsd] 164 + 165 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': 166 + resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} 167 + engines: {node: '>= 10'} 168 + cpu: [arm] 169 + os: [linux] 170 + 171 + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': 172 + resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} 173 + engines: {node: '>= 10'} 174 + cpu: [arm64] 175 + os: [linux] 176 + 177 + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': 178 + resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} 179 + engines: {node: '>= 10'} 180 + cpu: [arm64] 181 + os: [linux] 182 + 183 + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': 184 + resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} 185 + engines: {node: '>= 10'} 186 + cpu: [x64] 187 + os: [linux] 188 + 189 + '@tailwindcss/oxide-linux-x64-musl@4.1.17': 190 + resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} 191 + engines: {node: '>= 10'} 192 + cpu: [x64] 193 + os: [linux] 194 + 195 + '@tailwindcss/oxide-wasm32-wasi@4.1.17': 196 + resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} 197 + engines: {node: '>=14.0.0'} 198 + cpu: [wasm32] 199 + bundledDependencies: 200 + - '@napi-rs/wasm-runtime' 201 + - '@emnapi/core' 202 + - '@emnapi/runtime' 203 + - '@tybys/wasm-util' 204 + - '@emnapi/wasi-threads' 205 + - tslib 206 + 207 + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': 208 + resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} 209 + engines: {node: '>= 10'} 210 + cpu: [arm64] 211 + os: [win32] 212 + 213 + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': 214 + resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} 215 + engines: {node: '>= 10'} 216 + cpu: [x64] 217 + os: [win32] 218 + 219 + '@tailwindcss/oxide@4.1.17': 220 + resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} 221 + engines: {node: '>= 10'} 222 + 223 + braces@3.0.3: 224 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 225 + engines: {node: '>=8'} 226 + 227 + detect-libc@1.0.3: 228 + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} 229 + engines: {node: '>=0.10'} 230 + hasBin: true 231 + 232 + detect-libc@2.1.2: 233 + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} 234 + engines: {node: '>=8'} 235 + 236 + enhanced-resolve@5.18.3: 237 + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} 238 + engines: {node: '>=10.13.0'} 239 + 240 + fill-range@7.1.1: 241 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 242 + engines: {node: '>=8'} 243 + 244 + graceful-fs@4.2.11: 245 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 246 + 247 + is-extglob@2.1.1: 248 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 249 + engines: {node: '>=0.10.0'} 250 + 251 + is-glob@4.0.3: 252 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 253 + engines: {node: '>=0.10.0'} 254 + 255 + is-number@7.0.0: 256 + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 257 + engines: {node: '>=0.12.0'} 258 + 259 + jiti@2.6.1: 260 + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} 261 + hasBin: true 262 + 263 + lightningcss-android-arm64@1.30.2: 264 + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} 265 + engines: {node: '>= 12.0.0'} 266 + cpu: [arm64] 267 + os: [android] 268 + 269 + lightningcss-darwin-arm64@1.30.2: 270 + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} 271 + engines: {node: '>= 12.0.0'} 272 + cpu: [arm64] 273 + os: [darwin] 274 + 275 + lightningcss-darwin-x64@1.30.2: 276 + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} 277 + engines: {node: '>= 12.0.0'} 278 + cpu: [x64] 279 + os: [darwin] 280 + 281 + lightningcss-freebsd-x64@1.30.2: 282 + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} 283 + engines: {node: '>= 12.0.0'} 284 + cpu: [x64] 285 + os: [freebsd] 286 + 287 + lightningcss-linux-arm-gnueabihf@1.30.2: 288 + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} 289 + engines: {node: '>= 12.0.0'} 290 + cpu: [arm] 291 + os: [linux] 292 + 293 + lightningcss-linux-arm64-gnu@1.30.2: 294 + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} 295 + engines: {node: '>= 12.0.0'} 296 + cpu: [arm64] 297 + os: [linux] 298 + 299 + lightningcss-linux-arm64-musl@1.30.2: 300 + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} 301 + engines: {node: '>= 12.0.0'} 302 + cpu: [arm64] 303 + os: [linux] 304 + 305 + lightningcss-linux-x64-gnu@1.30.2: 306 + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} 307 + engines: {node: '>= 12.0.0'} 308 + cpu: [x64] 309 + os: [linux] 310 + 311 + lightningcss-linux-x64-musl@1.30.2: 312 + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} 313 + engines: {node: '>= 12.0.0'} 314 + cpu: [x64] 315 + os: [linux] 316 + 317 + lightningcss-win32-arm64-msvc@1.30.2: 318 + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} 319 + engines: {node: '>= 12.0.0'} 320 + cpu: [arm64] 321 + os: [win32] 322 + 323 + lightningcss-win32-x64-msvc@1.30.2: 324 + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} 325 + engines: {node: '>= 12.0.0'} 326 + cpu: [x64] 327 + os: [win32] 328 + 329 + lightningcss@1.30.2: 330 + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} 331 + engines: {node: '>= 12.0.0'} 332 + 333 + magic-string@0.30.21: 334 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 335 + 336 + micromatch@4.0.8: 337 + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 338 + engines: {node: '>=8.6'} 339 + 340 + mri@1.2.0: 341 + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 342 + engines: {node: '>=4'} 343 + 344 + node-addon-api@7.1.1: 345 + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} 346 + 347 + picocolors@1.1.1: 348 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 349 + 350 + picomatch@2.3.1: 351 + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 352 + engines: {node: '>=8.6'} 353 + 354 + prettier-plugin-tailwindcss@0.6.14: 355 + resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==} 356 + engines: {node: '>=14.21.3'} 357 + peerDependencies: 358 + '@ianvs/prettier-plugin-sort-imports': '*' 359 + '@prettier/plugin-hermes': '*' 360 + '@prettier/plugin-oxc': '*' 361 + '@prettier/plugin-pug': '*' 362 + '@shopify/prettier-plugin-liquid': '*' 363 + '@trivago/prettier-plugin-sort-imports': '*' 364 + '@zackad/prettier-plugin-twig': '*' 365 + prettier: ^3.0 366 + prettier-plugin-astro: '*' 367 + prettier-plugin-css-order: '*' 368 + prettier-plugin-import-sort: '*' 369 + prettier-plugin-jsdoc: '*' 370 + prettier-plugin-marko: '*' 371 + prettier-plugin-multiline-arrays: '*' 372 + prettier-plugin-organize-attributes: '*' 373 + prettier-plugin-organize-imports: '*' 374 + prettier-plugin-sort-imports: '*' 375 + prettier-plugin-style-order: '*' 376 + prettier-plugin-svelte: '*' 377 + peerDependenciesMeta: 378 + '@ianvs/prettier-plugin-sort-imports': 379 + optional: true 380 + '@prettier/plugin-hermes': 381 + optional: true 382 + '@prettier/plugin-oxc': 383 + optional: true 384 + '@prettier/plugin-pug': 385 + optional: true 386 + '@shopify/prettier-plugin-liquid': 387 + optional: true 388 + '@trivago/prettier-plugin-sort-imports': 389 + optional: true 390 + '@zackad/prettier-plugin-twig': 391 + optional: true 392 + prettier-plugin-astro: 393 + optional: true 394 + prettier-plugin-css-order: 395 + optional: true 396 + prettier-plugin-import-sort: 397 + optional: true 398 + prettier-plugin-jsdoc: 399 + optional: true 400 + prettier-plugin-marko: 401 + optional: true 402 + prettier-plugin-multiline-arrays: 403 + optional: true 404 + prettier-plugin-organize-attributes: 405 + optional: true 406 + prettier-plugin-organize-imports: 407 + optional: true 408 + prettier-plugin-sort-imports: 409 + optional: true 410 + prettier-plugin-style-order: 411 + optional: true 412 + prettier-plugin-svelte: 413 + optional: true 414 + 415 + prettier@3.7.1: 416 + resolution: {integrity: sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==} 417 + engines: {node: '>=14'} 418 + hasBin: true 419 + 420 + source-map-js@1.2.1: 421 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 422 + engines: {node: '>=0.10.0'} 423 + 424 + tailwindcss@4.1.17: 425 + resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} 426 + 427 + tapable@2.3.0: 428 + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} 429 + engines: {node: '>=6'} 430 + 431 + to-regex-range@5.0.1: 432 + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 433 + engines: {node: '>=8.0'} 434 + 435 + typescript@5.9.3: 436 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 437 + engines: {node: '>=14.17'} 438 + hasBin: true 439 + 440 + snapshots: 441 + 442 + '@fontsource-variable/work-sans@5.2.8': {} 443 + 444 + '@jridgewell/gen-mapping@0.3.13': 445 + dependencies: 446 + '@jridgewell/sourcemap-codec': 1.5.5 447 + '@jridgewell/trace-mapping': 0.3.31 448 + 449 + '@jridgewell/remapping@2.3.5': 450 + dependencies: 451 + '@jridgewell/gen-mapping': 0.3.13 452 + '@jridgewell/trace-mapping': 0.3.31 453 + 454 + '@jridgewell/resolve-uri@3.1.2': {} 455 + 456 + '@jridgewell/sourcemap-codec@1.5.5': {} 457 + 458 + '@jridgewell/trace-mapping@0.3.31': 459 + dependencies: 460 + '@jridgewell/resolve-uri': 3.1.2 461 + '@jridgewell/sourcemap-codec': 1.5.5 462 + 463 + '@parcel/watcher-android-arm64@2.5.1': 464 + optional: true 465 + 466 + '@parcel/watcher-darwin-arm64@2.5.1': 467 + optional: true 468 + 469 + '@parcel/watcher-darwin-x64@2.5.1': 470 + optional: true 471 + 472 + '@parcel/watcher-freebsd-x64@2.5.1': 473 + optional: true 474 + 475 + '@parcel/watcher-linux-arm-glibc@2.5.1': 476 + optional: true 477 + 478 + '@parcel/watcher-linux-arm-musl@2.5.1': 479 + optional: true 480 + 481 + '@parcel/watcher-linux-arm64-glibc@2.5.1': 482 + optional: true 483 + 484 + '@parcel/watcher-linux-arm64-musl@2.5.1': 485 + optional: true 486 + 487 + '@parcel/watcher-linux-x64-glibc@2.5.1': 488 + optional: true 489 + 490 + '@parcel/watcher-linux-x64-musl@2.5.1': 491 + optional: true 492 + 493 + '@parcel/watcher-win32-arm64@2.5.1': 494 + optional: true 495 + 496 + '@parcel/watcher-win32-ia32@2.5.1': 497 + optional: true 498 + 499 + '@parcel/watcher-win32-x64@2.5.1': 500 + optional: true 501 + 502 + '@parcel/watcher@2.5.1': 503 + dependencies: 504 + detect-libc: 1.0.3 505 + is-glob: 4.0.3 506 + micromatch: 4.0.8 507 + node-addon-api: 7.1.1 508 + optionalDependencies: 509 + '@parcel/watcher-android-arm64': 2.5.1 510 + '@parcel/watcher-darwin-arm64': 2.5.1 511 + '@parcel/watcher-darwin-x64': 2.5.1 512 + '@parcel/watcher-freebsd-x64': 2.5.1 513 + '@parcel/watcher-linux-arm-glibc': 2.5.1 514 + '@parcel/watcher-linux-arm-musl': 2.5.1 515 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 516 + '@parcel/watcher-linux-arm64-musl': 2.5.1 517 + '@parcel/watcher-linux-x64-glibc': 2.5.1 518 + '@parcel/watcher-linux-x64-musl': 2.5.1 519 + '@parcel/watcher-win32-arm64': 2.5.1 520 + '@parcel/watcher-win32-ia32': 2.5.1 521 + '@parcel/watcher-win32-x64': 2.5.1 522 + 523 + '@tailwindcss/cli@4.1.17': 524 + dependencies: 525 + '@parcel/watcher': 2.5.1 526 + '@tailwindcss/node': 4.1.17 527 + '@tailwindcss/oxide': 4.1.17 528 + enhanced-resolve: 5.18.3 529 + mri: 1.2.0 530 + picocolors: 1.1.1 531 + tailwindcss: 4.1.17 532 + 533 + '@tailwindcss/node@4.1.17': 534 + dependencies: 535 + '@jridgewell/remapping': 2.3.5 536 + enhanced-resolve: 5.18.3 537 + jiti: 2.6.1 538 + lightningcss: 1.30.2 539 + magic-string: 0.30.21 540 + source-map-js: 1.2.1 541 + tailwindcss: 4.1.17 542 + 543 + '@tailwindcss/oxide-android-arm64@4.1.17': 544 + optional: true 545 + 546 + '@tailwindcss/oxide-darwin-arm64@4.1.17': 547 + optional: true 548 + 549 + '@tailwindcss/oxide-darwin-x64@4.1.17': 550 + optional: true 551 + 552 + '@tailwindcss/oxide-freebsd-x64@4.1.17': 553 + optional: true 554 + 555 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': 556 + optional: true 557 + 558 + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': 559 + optional: true 560 + 561 + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': 562 + optional: true 563 + 564 + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': 565 + optional: true 566 + 567 + '@tailwindcss/oxide-linux-x64-musl@4.1.17': 568 + optional: true 569 + 570 + '@tailwindcss/oxide-wasm32-wasi@4.1.17': 571 + optional: true 572 + 573 + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': 574 + optional: true 575 + 576 + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': 577 + optional: true 578 + 579 + '@tailwindcss/oxide@4.1.17': 580 + optionalDependencies: 581 + '@tailwindcss/oxide-android-arm64': 4.1.17 582 + '@tailwindcss/oxide-darwin-arm64': 4.1.17 583 + '@tailwindcss/oxide-darwin-x64': 4.1.17 584 + '@tailwindcss/oxide-freebsd-x64': 4.1.17 585 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17 586 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17 587 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.17 588 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.17 589 + '@tailwindcss/oxide-linux-x64-musl': 4.1.17 590 + '@tailwindcss/oxide-wasm32-wasi': 4.1.17 591 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 592 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 593 + 594 + braces@3.0.3: 595 + dependencies: 596 + fill-range: 7.1.1 597 + 598 + detect-libc@1.0.3: {} 599 + 600 + detect-libc@2.1.2: {} 601 + 602 + enhanced-resolve@5.18.3: 603 + dependencies: 604 + graceful-fs: 4.2.11 605 + tapable: 2.3.0 606 + 607 + fill-range@7.1.1: 608 + dependencies: 609 + to-regex-range: 5.0.1 610 + 611 + graceful-fs@4.2.11: {} 612 + 613 + is-extglob@2.1.1: {} 614 + 615 + is-glob@4.0.3: 616 + dependencies: 617 + is-extglob: 2.1.1 618 + 619 + is-number@7.0.0: {} 620 + 621 + jiti@2.6.1: {} 622 + 623 + lightningcss-android-arm64@1.30.2: 624 + optional: true 625 + 626 + lightningcss-darwin-arm64@1.30.2: 627 + optional: true 628 + 629 + lightningcss-darwin-x64@1.30.2: 630 + optional: true 631 + 632 + lightningcss-freebsd-x64@1.30.2: 633 + optional: true 634 + 635 + lightningcss-linux-arm-gnueabihf@1.30.2: 636 + optional: true 637 + 638 + lightningcss-linux-arm64-gnu@1.30.2: 639 + optional: true 640 + 641 + lightningcss-linux-arm64-musl@1.30.2: 642 + optional: true 643 + 644 + lightningcss-linux-x64-gnu@1.30.2: 645 + optional: true 646 + 647 + lightningcss-linux-x64-musl@1.30.2: 648 + optional: true 649 + 650 + lightningcss-win32-arm64-msvc@1.30.2: 651 + optional: true 652 + 653 + lightningcss-win32-x64-msvc@1.30.2: 654 + optional: true 655 + 656 + lightningcss@1.30.2: 657 + dependencies: 658 + detect-libc: 2.1.2 659 + optionalDependencies: 660 + lightningcss-android-arm64: 1.30.2 661 + lightningcss-darwin-arm64: 1.30.2 662 + lightningcss-darwin-x64: 1.30.2 663 + lightningcss-freebsd-x64: 1.30.2 664 + lightningcss-linux-arm-gnueabihf: 1.30.2 665 + lightningcss-linux-arm64-gnu: 1.30.2 666 + lightningcss-linux-arm64-musl: 1.30.2 667 + lightningcss-linux-x64-gnu: 1.30.2 668 + lightningcss-linux-x64-musl: 1.30.2 669 + lightningcss-win32-arm64-msvc: 1.30.2 670 + lightningcss-win32-x64-msvc: 1.30.2 671 + 672 + magic-string@0.30.21: 673 + dependencies: 674 + '@jridgewell/sourcemap-codec': 1.5.5 675 + 676 + micromatch@4.0.8: 677 + dependencies: 678 + braces: 3.0.3 679 + picomatch: 2.3.1 680 + 681 + mri@1.2.0: {} 682 + 683 + node-addon-api@7.1.1: {} 684 + 685 + picocolors@1.1.1: {} 686 + 687 + picomatch@2.3.1: {} 688 + 689 + prettier-plugin-tailwindcss@0.6.14(prettier@3.7.1): 690 + dependencies: 691 + prettier: 3.7.1 692 + 693 + prettier@3.7.1: {} 694 + 695 + source-map-js@1.2.1: {} 696 + 697 + tailwindcss@4.1.17: {} 698 + 699 + tapable@2.3.0: {} 700 + 701 + to-regex-range@5.0.1: 702 + dependencies: 703 + is-number: 7.0.0 704 + 705 + typescript@5.9.3: {}
+2
pnpm-workspace.yaml
··· 1 + onlyBuiltDependencies: 2 + - '@parcel/watcher'
+112
priv/gettext/en/LC_MESSAGES/errors.po
··· 1 + ## `msgid`s in this file come from POT (.pot) files. 2 + ## 3 + ## Do not add, change, or remove `msgid`s manually here as 4 + ## they're tied to the ones in the corresponding POT file 5 + ## (with the same domain). 6 + ## 7 + ## Use `mix gettext.extract --merge` or `mix gettext.merge` 8 + ## to merge POT files into PO files. 9 + msgid "" 10 + msgstr "" 11 + "Language: en\n" 12 + 13 + ## From Ecto.Changeset.cast/4 14 + msgid "can't be blank" 15 + msgstr "" 16 + 17 + ## From Ecto.Changeset.unique_constraint/3 18 + msgid "has already been taken" 19 + msgstr "" 20 + 21 + ## From Ecto.Changeset.put_change/3 22 + msgid "is invalid" 23 + msgstr "" 24 + 25 + ## From Ecto.Changeset.validate_acceptance/3 26 + msgid "must be accepted" 27 + msgstr "" 28 + 29 + ## From Ecto.Changeset.validate_format/3 30 + msgid "has invalid format" 31 + msgstr "" 32 + 33 + ## From Ecto.Changeset.validate_subset/3 34 + msgid "has an invalid entry" 35 + msgstr "" 36 + 37 + ## From Ecto.Changeset.validate_exclusion/3 38 + msgid "is reserved" 39 + msgstr "" 40 + 41 + ## From Ecto.Changeset.validate_confirmation/3 42 + msgid "does not match confirmation" 43 + msgstr "" 44 + 45 + ## From Ecto.Changeset.no_assoc_constraint/3 46 + msgid "is still associated with this entry" 47 + msgstr "" 48 + 49 + msgid "are still associated with this entry" 50 + msgstr "" 51 + 52 + ## From Ecto.Changeset.validate_length/3 53 + msgid "should have %{count} item(s)" 54 + msgid_plural "should have %{count} item(s)" 55 + msgstr[0] "" 56 + msgstr[1] "" 57 + 58 + msgid "should be %{count} character(s)" 59 + msgid_plural "should be %{count} character(s)" 60 + msgstr[0] "" 61 + msgstr[1] "" 62 + 63 + msgid "should be %{count} byte(s)" 64 + msgid_plural "should be %{count} byte(s)" 65 + msgstr[0] "" 66 + msgstr[1] "" 67 + 68 + msgid "should have at least %{count} item(s)" 69 + msgid_plural "should have at least %{count} item(s)" 70 + msgstr[0] "" 71 + msgstr[1] "" 72 + 73 + msgid "should be at least %{count} character(s)" 74 + msgid_plural "should be at least %{count} character(s)" 75 + msgstr[0] "" 76 + msgstr[1] "" 77 + 78 + msgid "should be at least %{count} byte(s)" 79 + msgid_plural "should be at least %{count} byte(s)" 80 + msgstr[0] "" 81 + msgstr[1] "" 82 + 83 + msgid "should have at most %{count} item(s)" 84 + msgid_plural "should have at most %{count} item(s)" 85 + msgstr[0] "" 86 + msgstr[1] "" 87 + 88 + msgid "should be at most %{count} character(s)" 89 + msgid_plural "should be at most %{count} character(s)" 90 + msgstr[0] "" 91 + msgstr[1] "" 92 + 93 + msgid "should be at most %{count} byte(s)" 94 + msgid_plural "should be at most %{count} byte(s)" 95 + msgstr[0] "" 96 + msgstr[1] "" 97 + 98 + ## From Ecto.Changeset.validate_number/3 99 + msgid "must be less than %{number}" 100 + msgstr "" 101 + 102 + msgid "must be greater than %{number}" 103 + msgstr "" 104 + 105 + msgid "must be less than or equal to %{number}" 106 + msgstr "" 107 + 108 + msgid "must be greater than or equal to %{number}" 109 + msgstr "" 110 + 111 + msgid "must be equal to %{number}" 112 + msgstr ""
+109
priv/gettext/errors.pot
··· 1 + ## This is a PO Template file. 2 + ## 3 + ## `msgid`s here are often extracted from source code. 4 + ## Add new translations manually only if they're dynamic 5 + ## translations that can't be statically extracted. 6 + ## 7 + ## Run `mix gettext.extract` to bring this file up to 8 + ## date. Leave `msgstr`s empty as changing them here has no 9 + ## effect: edit them in PO (`.po`) files instead. 10 + ## From Ecto.Changeset.cast/4 11 + msgid "can't be blank" 12 + msgstr "" 13 + 14 + ## From Ecto.Changeset.unique_constraint/3 15 + msgid "has already been taken" 16 + msgstr "" 17 + 18 + ## From Ecto.Changeset.put_change/3 19 + msgid "is invalid" 20 + msgstr "" 21 + 22 + ## From Ecto.Changeset.validate_acceptance/3 23 + msgid "must be accepted" 24 + msgstr "" 25 + 26 + ## From Ecto.Changeset.validate_format/3 27 + msgid "has invalid format" 28 + msgstr "" 29 + 30 + ## From Ecto.Changeset.validate_subset/3 31 + msgid "has an invalid entry" 32 + msgstr "" 33 + 34 + ## From Ecto.Changeset.validate_exclusion/3 35 + msgid "is reserved" 36 + msgstr "" 37 + 38 + ## From Ecto.Changeset.validate_confirmation/3 39 + msgid "does not match confirmation" 40 + msgstr "" 41 + 42 + ## From Ecto.Changeset.no_assoc_constraint/3 43 + msgid "is still associated with this entry" 44 + msgstr "" 45 + 46 + msgid "are still associated with this entry" 47 + msgstr "" 48 + 49 + ## From Ecto.Changeset.validate_length/3 50 + msgid "should have %{count} item(s)" 51 + msgid_plural "should have %{count} item(s)" 52 + msgstr[0] "" 53 + msgstr[1] "" 54 + 55 + msgid "should be %{count} character(s)" 56 + msgid_plural "should be %{count} character(s)" 57 + msgstr[0] "" 58 + msgstr[1] "" 59 + 60 + msgid "should be %{count} byte(s)" 61 + msgid_plural "should be %{count} byte(s)" 62 + msgstr[0] "" 63 + msgstr[1] "" 64 + 65 + msgid "should have at least %{count} item(s)" 66 + msgid_plural "should have at least %{count} item(s)" 67 + msgstr[0] "" 68 + msgstr[1] "" 69 + 70 + msgid "should be at least %{count} character(s)" 71 + msgid_plural "should be at least %{count} character(s)" 72 + msgstr[0] "" 73 + msgstr[1] "" 74 + 75 + msgid "should be at least %{count} byte(s)" 76 + msgid_plural "should be at least %{count} byte(s)" 77 + msgstr[0] "" 78 + msgstr[1] "" 79 + 80 + msgid "should have at most %{count} item(s)" 81 + msgid_plural "should have at most %{count} item(s)" 82 + msgstr[0] "" 83 + msgstr[1] "" 84 + 85 + msgid "should be at most %{count} character(s)" 86 + msgid_plural "should be at most %{count} character(s)" 87 + msgstr[0] "" 88 + msgstr[1] "" 89 + 90 + msgid "should be at most %{count} byte(s)" 91 + msgid_plural "should be at most %{count} byte(s)" 92 + msgstr[0] "" 93 + msgstr[1] "" 94 + 95 + ## From Ecto.Changeset.validate_number/3 96 + msgid "must be less than %{number}" 97 + msgstr "" 98 + 99 + msgid "must be greater than %{number}" 100 + msgstr "" 101 + 102 + msgid "must be less than or equal to %{number}" 103 + msgstr "" 104 + 105 + msgid "must be greater than or equal to %{number}" 106 + msgstr "" 107 + 108 + msgid "must be equal to %{number}" 109 + msgstr ""
+4
priv/repo/migrations/.formatter.exs
··· 1 + [ 2 + import_deps: [:ecto_sql], 3 + inputs: ["*.exs"] 4 + ]
+11
priv/repo/seeds.exs
··· 1 + # Script for populating the database. You can run it as: 2 + # 3 + # mix run priv/repo/seeds.exs 4 + # 5 + # Inside the script, you can read and write to any of your 6 + # repositories directly: 7 + # 8 + # Comet.Repo.insert!(%Comet.SomeSchema{}) 9 + # 10 + # We recommend using the bang functions (`insert!`, `update!` 11 + # and so on) as they will fail if something goes wrong.
priv/static/favicon.ico

This is a binary file and will not be displayed.

+6
priv/static/images/logo.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 71 48" fill="currentColor" aria-hidden="true"> 2 + <path 3 + d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.077.057c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728a13 13 0 0 0 1.182 1.106c1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zl-.006.006-.036-.004.021.018.012.053Za.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zl-.008.01.005.026.024.014Z" 4 + fill="#FD4F00" 5 + /> 6 + </svg>
+5
priv/static/robots.txt
··· 1 + # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 + # 3 + # To ban all spiders from the entire site uncomment the next two lines: 4 + # User-agent: * 5 + # Disallow: /
+14
test/comet_web/controllers/error_html_test.exs
··· 1 + defmodule CometWeb.ErrorHTMLTest do 2 + use CometWeb.ConnCase, async: true 3 + 4 + # Bring render_to_string/4 for testing custom views 5 + import Phoenix.Template, only: [render_to_string: 4] 6 + 7 + test "renders 404.html" do 8 + assert render_to_string(CometWeb.ErrorHTML, "404", "html", []) == "Not Found" 9 + end 10 + 11 + test "renders 500.html" do 12 + assert render_to_string(CometWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" 13 + end 14 + end
+12
test/comet_web/controllers/error_json_test.exs
··· 1 + defmodule CometWeb.ErrorJSONTest do 2 + use CometWeb.ConnCase, async: true 3 + 4 + test "renders 404" do 5 + assert CometWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} 6 + end 7 + 8 + test "renders 500" do 9 + assert CometWeb.ErrorJSON.render("500.json", %{}) == 10 + %{errors: %{detail: "Internal Server Error"}} 11 + end 12 + end
+8
test/comet_web/controllers/page_controller_test.exs
··· 1 + defmodule CometWeb.PageControllerTest do 2 + use CometWeb.ConnCase 3 + 4 + test "GET /", %{conn: conn} do 5 + conn = get(conn, ~p"/") 6 + assert html_response(conn, 200) =~ "Peace of mind from prototype to production" 7 + end 8 + end
+38
test/support/conn_case.ex
··· 1 + defmodule CometWeb.ConnCase do 2 + @moduledoc """ 3 + This module defines the test case to be used by 4 + tests that require setting up a connection. 5 + 6 + Such tests rely on `Phoenix.ConnTest` and also 7 + import other functionality to make it easier 8 + to build common data structures and query the data layer. 9 + 10 + Finally, if the test case interacts with the database, 11 + we enable the SQL sandbox, so changes done to the database 12 + are reverted at the end of every test. If you are using 13 + PostgreSQL, you can even run database tests asynchronously 14 + by setting `use CometWeb.ConnCase, async: true`, although 15 + this option is not recommended for other databases. 16 + """ 17 + 18 + use ExUnit.CaseTemplate 19 + 20 + using do 21 + quote do 22 + # The default endpoint for testing 23 + @endpoint CometWeb.Endpoint 24 + 25 + use CometWeb, :verified_routes 26 + 27 + # Import conveniences for testing with connections 28 + import Plug.Conn 29 + import Phoenix.ConnTest 30 + import CometWeb.ConnCase 31 + end 32 + end 33 + 34 + setup tags do 35 + Comet.DataCase.setup_sandbox(tags) 36 + {:ok, conn: Phoenix.ConnTest.build_conn()} 37 + end 38 + end
+58
test/support/data_case.ex
··· 1 + defmodule Comet.DataCase do 2 + @moduledoc """ 3 + This module defines the setup for tests requiring 4 + access to the application's data layer. 5 + 6 + You may define functions here to be used as helpers in 7 + your tests. 8 + 9 + Finally, if the test case interacts with the database, 10 + we enable the SQL sandbox, so changes done to the database 11 + are reverted at the end of every test. If you are using 12 + PostgreSQL, you can even run database tests asynchronously 13 + by setting `use Comet.DataCase, async: true`, although 14 + this option is not recommended for other databases. 15 + """ 16 + 17 + use ExUnit.CaseTemplate 18 + 19 + using do 20 + quote do 21 + alias Comet.Repo 22 + 23 + import Ecto 24 + import Ecto.Changeset 25 + import Ecto.Query 26 + import Comet.DataCase 27 + end 28 + end 29 + 30 + setup tags do 31 + Comet.DataCase.setup_sandbox(tags) 32 + :ok 33 + end 34 + 35 + @doc """ 36 + Sets up the sandbox based on the test tags. 37 + """ 38 + def setup_sandbox(tags) do 39 + pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Comet.Repo, shared: not tags[:async]) 40 + on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) 41 + end 42 + 43 + @doc """ 44 + A helper that transforms changeset errors into a map of messages. 45 + 46 + assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 47 + assert "password is too short" in errors_on(changeset).password 48 + assert %{password: ["password is too short"]} = errors_on(changeset) 49 + 50 + """ 51 + def errors_on(changeset) do 52 + Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 53 + Regex.replace(~r"%{(\w+)}", message, fn _, key -> 54 + opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() 55 + end) 56 + end) 57 + end 58 + end
+2
test/test_helper.exs
··· 1 + ExUnit.start() 2 + Ecto.Adapters.SQL.Sandbox.mode(Comet.Repo, :manual)