diff --git a/Cargo.lock b/Cargo.lock index 27be837..b24d466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "buckets-core" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "536e476a5181a9f8a12d65be91615f036a000a1b1a2eaacde1be78be866940fd" +checksum = "e6df107757f765b92fc260dd4b7c2df6c4e4646f79a4f4020c5ca5f249f52dcb" dependencies = [ "oiseau", "pathbufd", diff --git a/crates/app/src/public/html/communities/create_post.lisp b/crates/app/src/public/html/communities/create_post.lisp index 62a1c00..01e67be 100644 --- a/crates/app/src/public/html/communities/create_post.lisp +++ b/crates/app/src/public/html/communities/create_post.lisp @@ -84,7 +84,7 @@ ("class" "card_nest") (div ("class" "card small flex flex_row gap_2 items_center") - (text "{{ components::avatar(username=user.id, size=\"32px\", selector_type=\"id\") }}") + (text "{{ components::avatar(id=user.id, size=\"32px\") }}") (select ("id" "community_to_post_to") ("onchange" "update_community_avatar(event); check_community_supports_title(event)") diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index 5a57da8..0d931c9 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -1,8 +1,8 @@ -(text "{% macro avatar(username, size=\"24px\", selector_type=\"username\") -%}") +(text "{% macro avatar(id, size=\"24px\") -%}") (img - ("title" "{{ username }}'s avatar") - ("src" "/api/v1/auth/user/{{ username }}/avatar?selector_type={{ selector_type }}") - ("alt" "@{{ username }}") + ("title" "User avatar") + ("src" "{{ config.service_hosts.buckets }}/avatars/{{ id }}") + ("alt" "User avatar") ("class" "avatar shadow") ("loading" "lazy") ("style" "--size: {{ size }}")) @@ -20,11 +20,11 @@ ("class" "avatar shadow") ("loading" "lazy") ("style" "--size: {{ size }}")) -(text "{%- endif %} {%- endmacro %} {% macro banner(username, border_radius=\"var(--radius)\") -%}") +(text "{%- endif %} {%- endmacro %} {% macro banner(id, border_radius=\"var(--radius)\") -%}") (img - ("title" "{{ username }}'s banner") - ("src" "/api/v1/auth/user/{{ username }}/banner") - ("alt" "@{{ username }}'s banner") + ("title" "User banner") + ("src" "{{ config.service_hosts.buckets }}/banners/{{ id }}") + ("alt" "User banner") ("class" "banner shadow w_full") ("loading" "lazy") ("style" "border-radius: {{ border_radius }};")) @@ -372,7 +372,7 @@ (text "{% if not expect_repost -%}") (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, size=\"52px\", selector_type=\"username\") }}")) + (text "{{ self::avatar(id=owner.id, size=\"52px\") }}")) (text "{%- endif %}") (div ("class" "flex flex_col w_full gap_1 post_right {% if expect_repost -%}repost{%- endif %}") @@ -381,7 +381,7 @@ (text "{% if expect_repost -%}") (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, size=\"24px\", selector_type=\"username\") }}")) + (text "{{ self::avatar(id=owner.id, size=\"24px\") }}")) (text "{%- endif %}") (span ; ("class" "name") @@ -542,7 +542,7 @@ (a ("href" "/@{{ owner.username }}") ("class" "mobile") - (text "{{ self::avatar(username=owner.username, size=\"24px\", selector_type=\"username\") }}")) + (text "{{ self::avatar(id=owner.id, size=\"24px\") }}")) (span ("class" "name") (text "{{ self::full_username(user=owner) }}")) @@ -561,7 +561,7 @@ ("style" "min-width: 200px") (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, size=\"64px\", selector_type=\"username\") }}")) + (text "{{ self::avatar(id=owner.id, size=\"64px\") }}")) (div ("class" "fade flex flex_col") ("style" "font-size: 12px") @@ -617,10 +617,10 @@ (div ("class" "card small") ("style" "padding: 0") - (text "{{ self::banner(username=user.username, border_radius=\"0px\") }}")) + (text "{{ self::banner(id=user.id, border_radius=\"0px\") }}")) (div ("class" "card secondary flex items_center gap_4") - (text "{{ self::avatar(username=user.username, size=\"24px\") }}") + (text "{{ self::avatar(id=user.id, size=\"24px\") }}") (b ("class" "flex items_center gap_2") (text "{{ self::username(user=user) }}") @@ -768,11 +768,11 @@ ("class" "avatar shadow") ("loading" "lazy") ("style" "--size: 52px")) - (text "{% else %} {{ self::avatar(username=\"anonymous\", selector_type=\"username\", size=\"52px\") }} {%- endif %}")) + (text "{% else %} {{ self::avatar(id=\"0\", size=\"52px\") }} {%- endif %}")) (text "{% else %}") (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, selector_type=\"username\", size=\"52px\") }}")) + (text "{{ self::avatar(id=owner.id, size=\"52px\") }}")) (text "{%- endif %}") (div ("class" "flex flex_col gap_1 w_full") @@ -1186,7 +1186,7 @@ (a ("href" "/@{{ user.username }}") ("target" "_top") - (text "{{ self::avatar(username=user.username, size=\"42px\") }}")) + (text "{{ self::avatar(id=user.id, size=\"42px\") }}")) (text "{%- endif %}") (div ("class" "flex flex_col gap_1 w_full") @@ -1354,7 +1354,7 @@ ("class" "flex gap_2 items_center card tiny user_plate {% if secondary -%}secondary{%- endif %} {% if full -%} w_full {%- endif %}") (a ("href" "/@{{ user.username }}") - (text "{{ self::avatar(username=user.username, size=\"42px\", selector_type=\"username\") }}")) + (text "{{ self::avatar(id=user.id, size=\"42px\") }}")) (div ("class" "flex justify_center flex_col") ("style" "{% if show_menu or show_kick -%}width: 60%{% else %}max-width: calc(100% - 42px - var(--pad-4)){%- endif %}") @@ -2127,7 +2127,7 @@ ; user info (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, size=\"24px\", selector_type=\"username\") }}")) + (text "{{ self::avatar(id=owner.id, size=\"24px\") }}")) (span ("class" "name") (text "{{ self::full_username(user=owner) }}")) @@ -2571,7 +2571,7 @@ ("class" "card lowered flex gap_2 flex_row") (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, size=\"32px\") }}")) + (text "{{ self::avatar(id=owner.username, size=\"32px\") }}")) (div ("class" "flex flex_col") (text "{{ self::full_username(user=owner) }}") @@ -2596,7 +2596,7 @@ ("class" "card flex gap_2 flex_row") (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, size=\"32px\") }}")) + (text "{{ self::avatar(id=owner.username, size=\"32px\") }}")) (div ("class" "flex flex_col") (text "{{ self::full_username(user=owner) }}") @@ -2612,14 +2612,14 @@ (text "{% for receiver in letter.receivers %}") (a ("href" "/api/v1/auth/user/find/{{ receiver }}") - (text "{{ components::avatar(username=receiver, selector_type=\"id\", size=\"18px\") }}")) + (text "{{ components::avatar(id=receiver, size=\"18px\") }}")) (text "{%- endfor %}")))) (text "{% else %}") (div ("class" "card small flex gap_2 flex_row") (a ("href" "/@{{ owner.username }}") - (text "{{ self::avatar(username=owner.username, size=\"24px\") }}")) + (text "{{ self::avatar(id=owner.username, size=\"24px\") }}")) (text "{{ self::full_username(user=owner) }}")) (text "{%- endif %}") diff --git a/crates/app/src/public/html/journals/app.lisp b/crates/app/src/public/html/journals/app.lisp index 48e9e12..0c9e3df 100644 --- a/crates/app/src/public/html/journals/app.lisp +++ b/crates/app/src/public/html/journals/app.lisp @@ -45,11 +45,11 @@ (meta ("name" "og:image") - ("content" "{{ config.host|safe }}/api/v1/auth/user/{{ owner.username }}/avatar?selector_type=username")) + ("content" "{{ config.service_hosts.buckets|safe }}/avatars/{{ owner.id }}")) (meta ("name" "twitter:image") - ("content" "{{ config.host|safe }}/api/v1/auth/user/{{ owner.username }}/avatar?selector_type=usernamev")) + ("content" "{{ config.service_hosts.buckets|safe }}/avatars/{{ owner.id }}")) (meta ("name" "twitter:card") @@ -148,7 +148,7 @@ (a ("class" "flex items_center") ("href" "/@{{ owner.username }}") - (text "{{ components::avatar(username=owner.username, selector_type=\"username\", size=\"18px\") }}")) + (text "{{ components::avatar(id=owner.id, size=\"18px\") }}")) (text "{% if (view_mode and owner) or not view_mode -%}") (a diff --git a/crates/app/src/public/html/littleweb/browser.lisp b/crates/app/src/public/html/littleweb/browser.lisp index e508d55..ee5872d 100644 --- a/crates/app/src/public/html/littleweb/browser.lisp +++ b/crates/app/src/public/html/littleweb/browser.lisp @@ -46,7 +46,7 @@ ("onclick" "trigger('atto::hooks::dropdown', [event])") ("exclude" "dropdown") ("style" "gap: var(--pad-1) !important") - (text "{{ components::avatar(username=user.username, size=\"24px\") }}") + (text "{{ components::avatar(id=user.id, size=\"24px\") }}") (icon_class (text "chevron-down") (text "dropdown_arrow"))) (text "{{ components::user_menu() }}")) diff --git a/crates/app/src/public/html/macros.lisp b/crates/app/src/public/html/macros.lisp index dff731a..98ad355 100644 --- a/crates/app/src/public/html/macros.lisp +++ b/crates/app/src/public/html/macros.lisp @@ -118,7 +118,7 @@ ("exclude" "dropdown") ("style" "gap: var(--pad-1) !important") ("title" "Account options") - (text "{{ components::avatar(username=user.username, size=\"24px\") }}") + (text "{{ components::avatar(id=user.id, size=\"24px\") }}") (icon_class (text "chevron-down") (text "dropdown_arrow"))) (text "{{ components::user_menu() }}")) diff --git a/crates/app/src/public/html/mail/compose.lisp b/crates/app/src/public/html/mail/compose.lisp index 430a8ff..088449a 100644 --- a/crates/app/src/public/html/mail/compose.lisp +++ b/crates/app/src/public/html/mail/compose.lisp @@ -92,7 +92,7 @@ const is_id = receiver.startsWith(\"id:\"); receiver = receiver.replaceAll(\"<\", \"<\").replaceAll(\">\", \">\").replace(\"id:\", \"\"); element.innerHTML += ``; } diff --git a/crates/app/src/public/html/misc/requests.lisp b/crates/app/src/public/html/misc/requests.lisp index 73d03ef..90e988d 100644 --- a/crates/app/src/public/html/misc/requests.lisp +++ b/crates/app/src/public/html/misc/requests.lisp @@ -64,7 +64,7 @@ ("class" "card small flex items_center gap_2") (a ("href" "/api/v1/auth/user/find/{{ request.id }}") - (text "{{ components::avatar(username=request.id, selector_type=\"id\") }}")) + (text "{{ components::avatar(id=request.id) }}")) (span (text "{{ text \"requests:label.user_follow_request\" }}"))) (div diff --git a/crates/app/src/public/html/mod/audit_log.lisp b/crates/app/src/public/html/mod/audit_log.lisp index 7ce09ae..d73b0d5 100644 --- a/crates/app/src/public/html/mod/audit_log.lisp +++ b/crates/app/src/public/html/mod/audit_log.lisp @@ -20,7 +20,7 @@ (a ("class" "card small flex items_center gap_2 flush") ("href" "/api/v1/auth/user/find/{{ item.moderator }}") - (text "{{ components::avatar(username=item.moderator, selector_type=\"id\") }}") + (text "{{ components::avatar(id=item.moderator) }}") (span (text "{{ item.moderator }}")) (span diff --git a/crates/app/src/public/html/mod/ip_bans.lisp b/crates/app/src/public/html/mod/ip_bans.lisp index d48b3cf..d37011c 100644 --- a/crates/app/src/public/html/mod/ip_bans.lisp +++ b/crates/app/src/public/html/mod/ip_bans.lisp @@ -28,7 +28,7 @@ (a ("class" "card small flex items_center gap_2 flush") ("href" "/api/v1/auth/user/find/{{ item.moderator }}") - (text "{{ components::avatar(username=item.moderator, selector_type=\"id\") }}") + (text "{{ components::avatar(id=item.moderator) }}") (span (text "{{ item.moderator }}")) (span diff --git a/crates/app/src/public/html/mod/reports.lisp b/crates/app/src/public/html/mod/reports.lisp index 8e9ddc6..6f3ba92 100644 --- a/crates/app/src/public/html/mod/reports.lisp +++ b/crates/app/src/public/html/mod/reports.lisp @@ -20,7 +20,7 @@ (a ("class" "card small flex items_center gap_2 flush") ("href" "/api/v1/auth/user/find/{{ item.owner }}") - (text "{{ components::avatar(username=item.owner, selector_type=\"id\") }}") + (text "{{ components::avatar(id=item.owner) }}") (span (text "{{ item.owner }}")) (span diff --git a/crates/app/src/public/html/mod/warnings.lisp b/crates/app/src/public/html/mod/warnings.lisp index d84ab53..f834aab 100644 --- a/crates/app/src/public/html/mod/warnings.lisp +++ b/crates/app/src/public/html/mod/warnings.lisp @@ -58,7 +58,7 @@ ("class" "flex items_center gap_2 flush") ("href" "/api/v1/auth/user/find/{{ item.moderator }}") ("title" "Moderator") - (text "{{ components::avatar(username=item.moderator, selector_type=\"id\") }}") + (text "{{ components::avatar(id=item.moderator) }}") (span (text "{{ item.moderator }}")) (span diff --git a/crates/app/src/public/html/post/post.lisp b/crates/app/src/public/html/post/post.lisp index e53ca2e..8fc25cd 100644 --- a/crates/app/src/public/html/post/post.lisp +++ b/crates/app/src/public/html/post/post.lisp @@ -20,11 +20,11 @@ (meta ("name" "og:image") - ("content" "{{ config.host|safe }}/api/v1/auth/user/{{ owner.username }}/avatar?selector_type=username")) + ("content" "{{ config.service_hosts.buckets|safe }}/avatars/{{ owner.id }}")) (meta ("name" "twitter:image") - ("content" "{{ config.host|safe }}/api/v1/auth/user/{{ owner.username }}/avatar?selector_type=usernamev")) + ("content" "{{ config.service_hosts.buckets|safe }}/avatars/{{ owner.id }}")) (meta ("name" "twitter:card") diff --git a/crates/app/src/public/html/post/reposts.lisp b/crates/app/src/public/html/post/reposts.lisp index 1890e1a..4e5a8db 100644 --- a/crates/app/src/public/html/post/reposts.lisp +++ b/crates/app/src/public/html/post/reposts.lisp @@ -71,7 +71,7 @@ ("class" "card flex items_center gap_2") (a ("href" "/@{{ post[1].username }}") - (text "{{ components::avatar(username=post[1].username, size=\"24px\", selector_type=\"username\") }}")) + (text "{{ components::avatar(id=post[1].id, size=\"24px\") }}")) (div ("class" "name") (text "{{ components::full_username(user=post[1]) }}"))) diff --git a/crates/app/src/public/html/profile/banned.lisp b/crates/app/src/public/html/profile/banned.lisp index 17f540b..ab8f40a 100644 --- a/crates/app/src/public/html/profile/banned.lisp +++ b/crates/app/src/public/html/profile/banned.lisp @@ -11,7 +11,7 @@ ("class" "card small flex items_center justify_between gap_2") (div ("class" "flex items_center gap_2") - (text "{{ components::avatar(username=profile.username, size=\"24px\") }}") + (text "{{ components::avatar(id=profile.id, size=\"24px\") }}") (span (text "{{ profile.username }}"))) (b diff --git a/crates/app/src/public/html/profile/base.lisp b/crates/app/src/public/html/profile/base.lisp index 1fde607..7684f87 100644 --- a/crates/app/src/public/html/profile/base.lisp +++ b/crates/app/src/public/html/profile/base.lisp @@ -24,11 +24,11 @@ (meta ("name" "og:image") - ("content" "{{ config.host|safe }}/api/v1/auth/user/{{ profile.username }}/avatar?selector_type=username")) + ("content" "{{ config.service_hosts.buckets|safe }}/avatars/{{ profile.id }}")) (meta ("name" "twitter:image") - ("content" "{{ config.host|safe }}/api/v1/auth/user/{{ profile.username }}/avatar?selector_type=username")) + ("content" "{{ config.service_hosts.buckets|safe }}/avatars/{{ profile.id }}")) (meta ("name" "twitter:card") @@ -46,7 +46,7 @@ (article (div ("class" "content_container flex flex_col gap_4") - (text "{{ components::banner(username=profile.username) }}") + (text "{{ components::banner(id=profile.id) }}") (div ("class" "w_full flex gap_4 flex_collapse") (div @@ -57,7 +57,7 @@ (div ("class" "card flex gap_2") ("id" "user_avatar_and_name") - (text "{{ components::avatar(username=profile.username,size=\"72px\") }}") + (text "{{ components::avatar(id=profile.id,size=\"72px\") }}") (div ("class" "flex flex_col") (h3 diff --git a/crates/app/src/public/html/profile/blocked.lisp b/crates/app/src/public/html/profile/blocked.lisp index 919cbcb..4d1922b 100644 --- a/crates/app/src/public/html/profile/blocked.lisp +++ b/crates/app/src/public/html/profile/blocked.lisp @@ -11,7 +11,7 @@ ("class" "card small flex items_center justify_between gap_2") (div ("class" "flex items_center gap_2") - (text "{{ components::avatar(username=profile.username, size=\"24px\") }}") + (text "{{ components::avatar(id=profile.id, size=\"24px\") }}") (span (text "{{ profile.username }}"))) (b diff --git a/crates/app/src/public/html/profile/outbox.lisp b/crates/app/src/public/html/profile/outbox.lisp index a8c8563..40efd93 100644 --- a/crates/app/src/public/html/profile/outbox.lisp +++ b/crates/app/src/public/html/profile/outbox.lisp @@ -30,7 +30,7 @@ ("class" "flex items_center gap_2 flush") ("href" "/api/v1/auth/user/find/{{ question[0].receiver }}") (icon (text "send")) - (text "{{ components::avatar(username=question[0].receiver, selector_type='id') }}")) + (text "{{ components::avatar(id=question[0].receiver) }}")) ; show button to delete question (button diff --git a/crates/app/src/public/html/profile/private.lisp b/crates/app/src/public/html/profile/private.lisp index e0cfee8..4b1fdea 100644 --- a/crates/app/src/public/html/profile/private.lisp +++ b/crates/app/src/public/html/profile/private.lisp @@ -11,7 +11,7 @@ ("class" "card small flex items_center justify_between gap_2") (div ("class" "flex items_center gap_2") - (text "{{ components::avatar(username=profile.username, size=\"24px\") }}") + (text "{{ components::avatar(id=profile.id, size=\"24px\") }}") (span (text "{{ profile.username }}"))) (b diff --git a/crates/app/src/public/html/profile/settings.lisp b/crates/app/src/public/html/profile/settings.lisp index 1628288..1834efd 100644 --- a/crates/app/src/public/html/profile/settings.lisp +++ b/crates/app/src/public/html/profile/settings.lisp @@ -466,7 +466,7 @@ ("class" "card secondary flex flex_wrap gap_2 items_center justify_between") (div ("class" "flex gap_2") - (text "{{ components::avatar(username=user.username) }} {{ components::full_username(user=user) }}")) + (text "{{ components::avatar(id=user.id) }} {{ components::full_username(user=user) }}")) (div ("class" "flex gap_2") (button @@ -522,7 +522,7 @@ ("class" "card secondary flex flex_wrap gap_2 items_center justify_between") (div ("class" "flex gap_2") - (text "{{ components::avatar(username=user.username) }} {{ components::full_username(user=user) }}")) + (text "{{ components::avatar(id=user.id) }} {{ components::full_username(user=user) }}")) (div ("class" "flex gap_2") (button @@ -594,7 +594,7 @@ ("class" "card secondary flex flex_wrap gap_2 items_center justify_between") (div ("class" "flex gap_2") - (text "{{ components::avatar(username=user.username) }} {{ components::full_username(user=user) }}")) + (text "{{ components::avatar(id=user.id) }} {{ components::full_username(user=user) }}")) (div ("class" "flex gap_2") (a diff --git a/crates/app/src/public/html/profile/warning.lisp b/crates/app/src/public/html/profile/warning.lisp index a1397e7..fe36df0 100644 --- a/crates/app/src/public/html/profile/warning.lisp +++ b/crates/app/src/public/html/profile/warning.lisp @@ -11,7 +11,7 @@ ("class" "card small flex items_center justify_between gap_2") (div ("class" "flex items_center gap_2") - (text "{{ components::avatar(username=profile.username, size=\"24px\") }}") + (text "{{ components::avatar(id=profile.id, size=\"24px\") }}") (span (text "{{ profile.username }}"))) (b diff --git a/crates/app/src/public/html/root.lisp b/crates/app/src/public/html/root.lisp index 636350d..68bfc13 100644 --- a/crates/app/src/public/html/root.lisp +++ b/crates/app/src/public/html/root.lisp @@ -31,6 +31,9 @@ name: \"tetratto\", ns_store: {}, classes: {}, + service_hosts: { + buckets: \"{{ config.service_hosts.buckets }}\", + } }; globalThis.no_policy = false; diff --git a/crates/app/src/public/html/stacks/add_user.lisp b/crates/app/src/public/html/stacks/add_user.lisp index 5ae6f90..5804496 100644 --- a/crates/app/src/public/html/stacks/add_user.lisp +++ b/crates/app/src/public/html/stacks/add_user.lisp @@ -9,7 +9,7 @@ ("class" "card_nest") (div ("class" "card small flex items_center gap_2") - (text "{{ components::avatar(username=add_user.username, size=\"24px\") }}") + (text "{{ components::avatar(id=add_user.username, size=\"24px\") }}") (text "{{ components::full_username(user=add_user) }}")) (div ("class" "card flex flex_col gap_2") diff --git a/crates/app/src/public/html/stacks/feed.lisp b/crates/app/src/public/html/stacks/feed.lisp index 392417e..ca19b74 100644 --- a/crates/app/src/public/html/stacks/feed.lisp +++ b/crates/app/src/public/html/stacks/feed.lisp @@ -14,7 +14,7 @@ ("class" "flex items_center gap_2") (a ("href" "/api/v1/auth/user/find/{{ stack.owner }}") - (text "{{ components::avatar(username=stack.owner, selector_type=\"id\") }}")) + (text "{{ components::avatar(id=stack.owner) }}")) (span (text "{{ stack.name }}"))) (div @@ -81,7 +81,7 @@ (a ("href" "/api/v1/auth/user/find/{{ user }}") ("class" "flush") - (text "{{ components::avatar(username=user, selector_type=\"id\", size=\"24px\") }}")) + (text "{{ components::avatar(id=user, size=\"24px\") }}")) (text "{% endfor %}")) (text "{%- endif %}") diff --git a/crates/app/src/public/html/stacks/manage.lisp b/crates/app/src/public/html/stacks/manage.lisp index 31c6af8..fe61525 100644 --- a/crates/app/src/public/html/stacks/manage.lisp +++ b/crates/app/src/public/html/stacks/manage.lisp @@ -146,7 +146,7 @@ ("class" "card secondary flex flex_wrap gap_2 items_center justify_between") (div ("class" "flex gap_2") - (text "{{ components::avatar(username=user.username) }} {{ components::full_username(user=user) }}")) + (text "{{ components::avatar(id=user.id) }} {{ components::full_username(user=user) }}")) (button ("class" "lowered small red") ("onclick" "remove_user('{{ user.username }}')") diff --git a/crates/app/src/public/js/atto.js b/crates/app/src/public/js/atto.js index 952e005..97850cf 100644 --- a/crates/app/src/public/js/atto.js +++ b/crates/app/src/public/js/atto.js @@ -1155,7 +1155,7 @@ ${option.input_element_type === "textarea" ? `${option.value}` : ""} const data = await (await fetch(`${src}/json`)).json(); document .getElementById("lightbox_img") - .setAttribute("alt", data.payload.alt); + .setAttribute("alt", data.payload.metadata.alt || "Image upload"); document.getElementById("lightbox_img").title = data.payload.alt; document.getElementById("lightbox_img_a").href = src; diff --git a/crates/app/src/public/js/me.js b/crates/app/src/public/js/me.js index 708baf4..ef41ad8 100644 --- a/crates/app/src/public/js/me.js +++ b/crates/app/src/public/js/me.js @@ -503,7 +503,7 @@ new Notification(inner_data.title, { body: inner_data.content, icon: matches[1] - ? `/api/v1/auth/user/${matches[1]}/avatar?selector_type=id` + ? `${_app_base.service_hosts.buckets}/avatars/${matches[1]}` : "/public/favicon.svg", lang: "en-US", }); @@ -727,14 +727,6 @@ for (const token of Object.entries($.LOGIN_ACCOUNT_TOKENS)) { element.innerHTML += `
diff --git a/crates/app/src/routes/api/v1/auth/images.rs b/crates/app/src/routes/api/v1/auth/images.rs index cbaf344..1e6087d 100644 --- a/crates/app/src/routes/api/v1/auth/images.rs +++ b/crates/app/src/routes/api/v1/auth/images.rs @@ -1,17 +1,12 @@ -use axum::{ - Extension, Json, - body::Body, - extract::{Path, Query}, - response::IntoResponse, -}; +use axum::{Extension, Json, response::IntoResponse}; use crate::cookie::CookieJar; -use pathbufd::{PathBufD, pathd}; -use serde::Deserialize; -use std::{ - fs::{File, exists}, - io::Read, +use pathbufd::PathBufD; +use std::{fs::File, io::Read}; +use tetratto_core::model::{ + permissions::FinePermission, + uploads::{MediaType, MediaUpload}, + ApiReturn, Error, }; -use tetratto_core::model::{permissions::FinePermission, ApiReturn, Error}; use crate::{ State, @@ -29,139 +24,6 @@ pub fn read_image(path: PathBufD) -> Vec { bytes } -#[derive(Deserialize, PartialEq, Eq)] -pub enum AvatarSelectorType { - #[serde(alias = "username")] - Username, - #[serde(alias = "id")] - Id, -} - -#[derive(Deserialize)] -pub struct AvatarSelectorQuery { - pub selector_type: AvatarSelectorType, -} - -/// Get a profile's avatar image -/// `/api/v1/auth/user/{id}/avatar` -pub async fn avatar_request( - Path(selector): Path, - Extension(data): Extension, - Query(req): Query, -) -> impl IntoResponse { - let data = &(data.read().await).0; - - let user = match if req.selector_type == AvatarSelectorType::Id { - data.get_user_by_id(match selector.parse::() { - Ok(d) => d, - Err(_) => { - return Err(( - [("Content-Type", "image/svg+xml")], - Body::from(read_image(PathBufD::current().extend(&[ - data.0.0.dirs.media.as_str(), - "images", - "default-avatar.svg", - ]))), - )); - } - }) - .await - } else { - data.get_user_by_username(&selector).await - } { - Ok(ua) => ua, - Err(_) => { - return Err(( - [("Content-Type", "image/svg+xml")], - Body::from(read_image(PathBufD::current().extend(&[ - data.0.0.dirs.media.as_str(), - "images", - "default-avatar.svg", - ]))), - )); - } - }; - - let mime = if user.settings.avatar_mime.is_empty() { - "image/avif" - } else { - &user.settings.avatar_mime - }; - - let path = PathBufD::current().extend(&[ - data.0.0.dirs.media.as_str(), - "avatars", - &format!("{}.{}", &(user.id as i64), mime.replace("image/", "")), - ]); - - if !exists(&path).unwrap() { - return Err(( - [("Content-Type", "image/svg+xml")], - Body::from(read_image(PathBufD::current().extend(&[ - data.0.0.dirs.media.as_str(), - "images", - "default-avatar.svg", - ]))), - )); - } - - Ok(( - [("Content-Type".to_string(), mime.to_owned())], - Body::from(read_image(path)), - )) -} - -/// Get a profile's banner image -/// `/api/v1/auth/user/{id}/banner` -pub async fn banner_request( - Path(username): Path, - Extension(data): Extension, -) -> impl IntoResponse { - let data = &(data.read().await).0; - - let user = match data.get_user_by_username(&username).await { - Ok(ua) => ua, - Err(_) => { - return Err(( - [("Content-Type", "image/svg+xml")], - Body::from(read_image(PathBufD::current().extend(&[ - data.0.0.dirs.media.as_str(), - "images", - "default-banner.svg", - ]))), - )); - } - }; - - let mime = if user.settings.banner_mime.is_empty() { - "image/avif" - } else { - &user.settings.banner_mime - }; - - let path = PathBufD::current().extend(&[ - data.0.0.dirs.media.as_str(), - "banners", - &format!("{}.{}", &(user.id as i64), mime.replace("image/", "")), - ]); - - if !exists(&path).unwrap() { - return Err(( - [("Content-Type", "image/svg+xml")], - Body::from(read_image(PathBufD::current().extend(&[ - data.0.0.dirs.media.as_str(), - "images", - "default-banner.svg", - ]))), - )); - } - - Ok(( - [("Content-Type".to_string(), mime.to_owned())], - Body::from(read_image(path)), - )) -} - pub const MAXIMUM_FILE_SIZE: usize = 8388608; pub const MAXIMUM_GIF_FILE_SIZE: usize = 2097152; @@ -173,44 +35,57 @@ pub async fn upload_avatar_request( ) -> impl IntoResponse { // get user from token let data = &(data.read().await).0; - let mut auth_user = match get_user_from_token!(jar, data) { + let auth_user = match get_user_from_token!(jar, data) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; - if img.1 == "image/gif" && !auth_user.permissions.check(FinePermission::SUPPORTER) { - return Json(Error::RequiresSupporter.into()); - } - - let mime = if img.1 == "image/gif" { - "image/gif" + if img.1 == "image/gif" { + if !auth_user.permissions.check(FinePermission::SUPPORTER) { + return Json(Error::RequiresSupporter.into()); + } else { + if img.0.len() > MAXIMUM_GIF_FILE_SIZE { + return Json(Error::FileTooLarge.into()); + } + } } else { - "image/avif" - }; - - if auth_user.settings.avatar_mime != mime { - // mime changed; delete old image - let path = pathd!( - "{}/avatars/{}.{}", - data.0.0.dirs.media, - &auth_user.id, - auth_user.settings.avatar_mime.replace("image/", "") - ); - - if std::fs::exists(&path).unwrap() { - std::fs::remove_file(path).unwrap(); + if img.0.len() > MAXIMUM_FILE_SIZE { + return Json(Error::FileTooLarge.into()); } } - let path = pathd!( - "{}/avatars/{}.{}", - data.0.0.dirs.media, - &auth_user.id, - mime.replace("image/", "") + // delete old upload + if let Ok(u) = data + .2 + .get_upload_by_id_bucket(auth_user.id, "avatars") + .await + { + if let Err(e) = data.2.delete_upload_with_bucket(u.id, "avatars").await { + return Json(Error::MiscError(e.to_string()).into()); + } + } + + // create new upload + let mut new_upload = MediaUpload::new( + if img.1 == "image/gif" { + MediaType::Gif + } else { + MediaType::Avif + }, + auth_user.id, + "avatars".to_string(), ); + new_upload.id = auth_user.id; + + let upload = match data.2.create_upload(new_upload).await { + Ok(x) => x, + Err(e) => return Json(Error::MiscError(e.to_string()).into()), + }; + // upload image (gif) - if mime == "image/gif" { + let path = upload.path(&data.2.0.0.directory); + if img.1 == "image/gif" { // gif image, don't encode if img.0.len() > MAXIMUM_GIF_FILE_SIZE { return Json(Error::FileTooLarge.into()); @@ -218,15 +93,6 @@ pub async fn upload_avatar_request( std::fs::write(&path, img.0).unwrap(); - // update user settings - auth_user.settings.avatar_mime = "image/gif".to_string(); - if let Err(e) = data - .update_user_settings(auth_user.id, auth_user.settings) - .await - { - return Json(e.into()); - } - // ... return Json(ApiReturn { ok: true, @@ -235,28 +101,8 @@ pub async fn upload_avatar_request( }); } - // check file size - if img.0.len() > MAXIMUM_FILE_SIZE { - return Json(Error::FileTooLarge.into()); - } - - // update user settings - auth_user.settings.avatar_mime = "image/avif".to_string(); - if let Err(e) = data - .update_user_settings(auth_user.id, auth_user.settings) - .await - { - return Json(e.into()); - } - // upload image - let mut bytes = Vec::new(); - - for byte in img.0 { - bytes.push(byte); - } - - match save_buffer(&path, bytes, image::ImageFormat::Avif) { + match save_buffer(&path.to_string(), img.0.to_vec(), image::ImageFormat::Avif) { Ok(_) => Json(ApiReturn { ok: true, message: "Avatar uploaded. It might take a bit to update".to_string(), @@ -274,44 +120,57 @@ pub async fn upload_banner_request( ) -> impl IntoResponse { // get user from token let data = &(data.read().await).0; - let mut auth_user = match get_user_from_token!(jar, data) { + let auth_user = match get_user_from_token!(jar, data) { Some(ua) => ua, None => return Json(Error::NotAllowed.into()), }; - if img.1 == "image/gif" && !auth_user.permissions.check(FinePermission::SUPPORTER) { - return Json(Error::RequiresSupporter.into()); - } - - let mime = if img.1 == "image/gif" { - "image/gif" + if img.1 == "image/gif" { + if !auth_user.permissions.check(FinePermission::SUPPORTER) { + return Json(Error::RequiresSupporter.into()); + } else { + if img.0.len() > MAXIMUM_GIF_FILE_SIZE { + return Json(Error::FileTooLarge.into()); + } + } } else { - "image/avif" - }; - - if auth_user.settings.banner_mime != mime { - // mime changed; delete old image - let path = pathd!( - "{}/banners/{}.{}", - data.0.0.dirs.media, - &auth_user.id, - auth_user.settings.banner_mime.replace("image/", "") - ); - - if std::fs::exists(&path).unwrap() { - std::fs::remove_file(path).unwrap(); + if img.0.len() > MAXIMUM_FILE_SIZE { + return Json(Error::FileTooLarge.into()); } } - let path = pathd!( - "{}/banners/{}.{}", - data.0.0.dirs.media, - &auth_user.id, - mime.replace("image/", "") + // delete old upload + if let Ok(u) = data + .2 + .get_upload_by_id_bucket(auth_user.id, "banners") + .await + { + if let Err(e) = data.2.delete_upload_with_bucket(u.id, "banners").await { + return Json(Error::MiscError(e.to_string()).into()); + } + } + + // create new upload + let mut new_upload = MediaUpload::new( + if img.1 == "image/gif" { + MediaType::Gif + } else { + MediaType::Avif + }, + auth_user.id, + "banners".to_string(), ); + new_upload.id = auth_user.id; + + let upload = match data.2.create_upload(new_upload).await { + Ok(x) => x, + Err(e) => return Json(Error::MiscError(e.to_string()).into()), + }; + // upload image (gif) - if mime == "image/gif" { + let path = upload.path(&data.2.0.0.directory); + if img.1 == "image/gif" { // gif image, don't encode if img.0.len() > MAXIMUM_GIF_FILE_SIZE { return Json(Error::FileTooLarge.into()); @@ -319,15 +178,6 @@ pub async fn upload_banner_request( std::fs::write(&path, img.0).unwrap(); - // update user settings - auth_user.settings.banner_mime = "image/gif".to_string(); - if let Err(e) = data - .update_user_settings(auth_user.id, auth_user.settings) - .await - { - return Json(e.into()); - } - // ... return Json(ApiReturn { ok: true, @@ -336,28 +186,8 @@ pub async fn upload_banner_request( }); } - // check file size - if img.0.len() > MAXIMUM_FILE_SIZE { - return Json(Error::FileTooLarge.into()); - } - - // update user settings - auth_user.settings.avatar_mime = "image/avif".to_string(); - if let Err(e) = data - .update_user_settings(auth_user.id, auth_user.settings) - .await - { - return Json(e.into()); - } - // upload image - let mut bytes = Vec::new(); - - for byte in img.0 { - bytes.push(byte); - } - - match save_buffer(&path, bytes, image::ImageFormat::Avif) { + match save_buffer(&path.to_string(), img.0.to_vec(), image::ImageFormat::Avif) { Ok(_) => Json(ApiReturn { ok: true, message: "Banner uploaded. It might take a bit to update".to_string(), diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index a5c92b3..5f94bd5 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -306,8 +306,6 @@ pub fn routes() -> Router { "/auth/user/me/policy_consent", post(auth::profile::policy_consent_request), ) - .route("/auth/user/{id}/avatar", get(auth::images::avatar_request)) - .route("/auth/user/{id}/banner", get(auth::images::banner_request)) .route("/auth/user/{id}/follow", post(auth::social::follow_request)) .route( "/auth/user/{id}/follow/toggle", diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index fefadbf..c5096c4 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -49,4 +49,4 @@ oiseau = { version = "0.1.2", default-features = false, features = [ ], optional = true } paste = { version = "1.0.15", optional = true } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } -buckets-core = "1.0.1" +buckets-core = "1.0.4" diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index c9ad94f..88097be 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -11,8 +11,6 @@ use crate::model::{ ACHIEVEMENTS, AppliedConfigType, }, }; -use pathbufd::PathBufD; -use std::fs::{exists, remove_file}; use tetratto_shared::{ hash::{hash_salted, salt}, unix_epoch_timestamp, @@ -618,35 +616,6 @@ impl DataManager { self.delete_app(app.id, &user).await?; } - // remove images - let avatar = PathBufD::current().extend(&[ - self.0.0.dirs.media.as_str(), - "avatars", - &format!( - "{}.{}", - &(user.id as i64), - user.settings.avatar_mime.replace("image/", "") - ), - ]); - - let banner = PathBufD::current().extend(&[ - self.0.0.dirs.media.as_str(), - "banners", - &format!( - "{}.{}", - &(user.id as i64), - user.settings.banner_mime.replace("image/", "") - ), - ]); - - if exists(&avatar).unwrap() { - remove_file(avatar).unwrap(); - } - - if exists(&banner).unwrap() { - remove_file(banner).unwrap(); - } - // delete uploads for upload in match self.2.get_uploads_by_owner_all(user.id).await { Ok(x) => x, diff --git a/crates/core/src/database/drivers/mod.rs b/crates/core/src/database/drivers/mod.rs index dc16c49..9e1e35f 100644 --- a/crates/core/src/database/drivers/mod.rs +++ b/crates/core/src/database/drivers/mod.rs @@ -18,6 +18,7 @@ impl DataManager { pub async fn new(config: Config) -> Result { let buckets_manager = BucketsManager::new(BucketsConfig { directory: format!("{}/{}", config.dirs.media, "uploads"), + bucket_defaults: HashMap::new(), database: config.database.clone(), }) .await diff --git a/crates/core/src/database/posts.rs b/crates/core/src/database/posts.rs index 7f6b9fe..0bda649 100644 --- a/crates/core/src/database/posts.rs +++ b/crates/core/src/database/posts.rs @@ -1657,7 +1657,7 @@ impl DataManager { let res = query_rows!( &conn, &format!( - "SELECT * FROM posts WHERE (owner = {} {query_string}) AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1 OFFSET $2", + "SELECT * FROM posts WHERE (owner = {id} OR owner = {} {query_string}) AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1 OFFSET $2", first.receiver ), &[&(batch as i64), &((page * batch) as i64)], diff --git a/crates/core/src/model/auth.rs b/crates/core/src/model/auth.rs index 0f4799f..00bcfeb 100644 --- a/crates/core/src/model/auth.rs +++ b/crates/core/src/model/auth.rs @@ -298,9 +298,6 @@ pub struct UserSettings { /// The user's status. Shows over connection info. #[serde(default)] pub status: String, - /// The mime type of the user's avatar. - #[serde(default = "mime_avif")] - pub avatar_mime: String, /// The mime type of the user's banner. #[serde(default = "mime_avif")] pub banner_mime: String, diff --git a/manual_migrations/avatars.js b/manual_migrations/avatars.js new file mode 100644 index 0000000..e3c5ce1 --- /dev/null +++ b/manual_migrations/avatars.js @@ -0,0 +1,54 @@ +import postgres from "npm:postgres"; +import { parse } from "npm:smol-toml"; +import { readdir, rename } from "node:fs/promises"; + +const config = parse(await Deno.readTextFile(Deno.cwd() + "/tetratto.toml"), { + integersAsBigInt: true, +}); + +const db = postgres({ + user: config.database.user, + password: config.database.password, + database: config.database.name, + hostname: config.database.url.split(":")[0], + port: config.database.url.split(":")[1], +}); + +let i = 0; +for (const fname of await readdir(Deno.cwd() + "/media/avatars")) { + const [uid, type] = fname.split("."); + + await db`INSERT INTO uploads VALUES (${BigInt(uid)}, ${BigInt(new Date().getTime())}, ${BigInt(uid)}, 'avatars', ${JSON.stringify( + { + what: type.charAt(0).toUpperCase() + type.slice(1), + }, + )});`; + + await rename( + Deno.cwd() + "/media/avatars/" + fname, + Deno.cwd() + "/media/uploads/avatars." + fname, + ); + + i += 1; + console.log(`done ${i}`); +} + +for (const fname of await readdir(Deno.cwd() + "/media/banners")) { + const [uid, type] = fname.split("."); + + await db`INSERT INTO uploads VALUES (${BigInt(uid)}, ${BigInt(new Date().getTime())}, ${BigInt(uid)}, 'banners', ${JSON.stringify( + { + what: type.charAt(0).toUpperCase() + type.slice(1), + }, + )});`; + + await rename( + Deno.cwd() + "/media/banners/" + fname, + Deno.cwd() + "/media/uploads/banners." + fname, + ); + + i += 1; + console.log(`done ${i}`); +} + +await db.end(); diff --git a/manual_migrations/uploads_pkey.sql b/manual_migrations/uploads_pkey.sql new file mode 100644 index 0000000..a8f3edc --- /dev/null +++ b/manual_migrations/uploads_pkey.sql @@ -0,0 +1,4 @@ +ALTER TABLE uploads +DROP CONSTRAINT uploads_pkey; + +ALTER TABLE uploads ADD CONSTRAINT uploads_pkey PRIMARY KEY (id, bucket);