add: blocked users page
This commit is contained in:
parent
6893aeedb5
commit
3706764437
18 changed files with 427 additions and 178 deletions
|
@ -15,7 +15,7 @@ serde = { version = "1.0.219", features = ["derive"] }
|
|||
tera = "1.20.0"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tower-http = { version = "0.6.3", features = ["trace", "fs", "catch-panic"] }
|
||||
tower-http = { version = "0.6.2", features = ["trace", "fs", "catch-panic"] }
|
||||
axum = { version = "0.8.4", features = ["macros", "ws"] }
|
||||
tokio = { version = "1.45.0", features = ["macros", "rt-multi-thread"] }
|
||||
axum-extra = { version = "0.10.1", features = ["cookie", "multipart"] }
|
||||
|
@ -35,7 +35,7 @@ cf-turnstile = "0.2.0"
|
|||
contrasted = "0.1.2"
|
||||
futures-util = "0.3.31"
|
||||
|
||||
redis = { version = "0.30.0", features = [
|
||||
redis = { version = "0.31.0", features = [
|
||||
"aio",
|
||||
"tokio-comp",
|
||||
], optional = true }
|
||||
|
|
|
@ -33,6 +33,9 @@ version = "1.0.0"
|
|||
"general:label.account_banned_body" = "Your account has been banned for violating our policies."
|
||||
"general:label.better_with_account" = "It's better with an account! Login or sign up to explore more."
|
||||
|
||||
"general:label.supporter_motivation" = "Become a supporter!"
|
||||
"general:action.become_supporter" = "Become supporter"
|
||||
|
||||
"dialog:action.okay" = "Ok"
|
||||
"dialog:action.continue" = "Continue"
|
||||
"dialog:action.cancel" = "Cancel"
|
||||
|
@ -136,6 +139,11 @@ version = "1.0.0"
|
|||
"settings:label.theme_lit" = "Theme lit"
|
||||
"settings:label.import" = "Import"
|
||||
"settings:label.export" = "Export"
|
||||
"settings:label.manage_blocks" = "Manage blocks"
|
||||
"settings:label.users" = "Users"
|
||||
"settings:tab.security" = "Security"
|
||||
"settings:tab.blocks" = "Blocks"
|
||||
"settings:tab.billing" = "Billing"
|
||||
|
||||
"mod_panel:label.open_reported_content" = "Open reported content"
|
||||
"mod_panel:label.manage_profile" = "Manage profile"
|
||||
|
|
|
@ -461,6 +461,39 @@ table ol {
|
|||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
/* supporter card */
|
||||
@property --border-angle {
|
||||
syntax: "<angle>";
|
||||
initial-value: 0deg;
|
||||
inherits: false;
|
||||
}
|
||||
|
||||
.supporter_ad {
|
||||
--border-angle: 0deg;
|
||||
padding: 2px;
|
||||
background-image:
|
||||
linear-gradient(var(--color-raised), var(--color-raised)),
|
||||
repeating-conic-gradient(
|
||||
from var(--border-angle),
|
||||
var(--color-primary) 0%,
|
||||
var(--color-primary-lowered) 50%
|
||||
);
|
||||
background-origin: border-box;
|
||||
background-clip: content-box, border-box;
|
||||
animation: 10s rotate_angle linear infinite;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes rotate_angle {
|
||||
from {
|
||||
--border-angle: 0deg;
|
||||
}
|
||||
|
||||
to {
|
||||
--border-angle: 360deg;
|
||||
}
|
||||
}
|
||||
|
||||
/* buttons */
|
||||
button,
|
||||
.button {
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if list|length >= 4 %} {{ components::supporter_ad(body="Become a
|
||||
supporter to create up to 10 communities!") }} {% endif %} {% endif %}
|
||||
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center justify-between gap-2">
|
||||
|
|
|
@ -1242,4 +1242,24 @@ show_kick=false, secondary=false) -%}
|
|||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
{% endif %} {%- endmacro %} {% macro supporter_ad(body="") -%} {% if
|
||||
config.stripe and not is_supporter %}
|
||||
<div
|
||||
class="card w-full supporter_ad"
|
||||
ui_ident="supporter_ad"
|
||||
onclick="window.location.href = '/settings#/account/billing'"
|
||||
>
|
||||
<div class="card w-full flex flex-wrap items-center gap-2 justify-between">
|
||||
{% if body %}
|
||||
<b>{{ body }}</b>
|
||||
{% else %}
|
||||
<b>{{ text "general:label.supporter_motivation" }}</b>
|
||||
{% endif %}
|
||||
|
||||
<a href="/settings#/account/billing" class="button small">
|
||||
{{ icon "heart" }}
|
||||
<span>{{ text "general:action.become_supporter" }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %} {%- endmacro %}
|
||||
|
|
|
@ -38,36 +38,24 @@
|
|||
|
||||
<div class="w-full flex flex-col gap-2" data-tab="account">
|
||||
<div class="card tertiary flex flex-col gap-2" id="account_settings">
|
||||
{% if config.stripe %}
|
||||
<div class="card-nest" ui_ident="supporter_card">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "star" }}
|
||||
<b>Supporter status</b>
|
||||
</div>
|
||||
<div class="pillmenu" ui_ident="account_settings_tabs">
|
||||
<a data-tab-button="account/security" href="#/account/security">
|
||||
{{ icon "user-lock" }}
|
||||
<span>{{ text "settings:tab.security" }}</span>
|
||||
</a>
|
||||
|
||||
<div class="card">
|
||||
{% if is_supporter %}
|
||||
<p>You <b>are</b> a supporter! Thank you for all that you do. You can manage your billing information below. <b>Please use your email address you supplied when paying to login to the billing portal.</b></p>
|
||||
<a href="{{ config.stripe.billing_portal_url }}" class="button quaternary" target="_blank">Manage billing</a>
|
||||
{% else %}
|
||||
<p>You're <b>not</b> currently a supporter! No pressure, but it helps us do some pretty cool things! As a supporter, you'll get:</p>
|
||||
<a data-tab-button="account/blocks" href="#/account/blocks">
|
||||
{{ icon "shield" }}
|
||||
<span>{{ text "settings:tab.blocks" }}</span>
|
||||
</a>
|
||||
|
||||
<ul style="margin-bottom: 1rem">
|
||||
<li>Vanity badge on profile</li>
|
||||
<li>Ability to upload gif avatars/banners</li>
|
||||
<li>Be an admin/owner of up to 10 communities</li>
|
||||
<li>Use custom CSS on your profile</li>
|
||||
<li>Ability to use community emojis outside of their community <b>(soon)</b></li>
|
||||
<li>Ability to upload and use gif emojis <b>(soon)</b></li>
|
||||
<li><b>Create infinite stack timelines</b></li>
|
||||
</ul>
|
||||
|
||||
<a href="{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}" class="button" target="_blank">Become a supporter</a>
|
||||
<span class="fade">Please use your <b>real email</b> when completing payment. It is required to manage your billing settings.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if config.stripe %}
|
||||
<a data-tab-button="account/billing" href="#/account/billing">
|
||||
{{ icon "credit-card" }}
|
||||
<span>{{ text "settings:tab.billing" }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-nest" ui_ident="home_timeline">
|
||||
<div class="card small">
|
||||
|
@ -153,62 +141,22 @@
|
|||
|
||||
<div class="card flex flex-col gap-2">
|
||||
<button id="notifications_button"></button>
|
||||
<span class="fade">Notifications require you to keep {{ config.name }} open in your browser for real-time updates. This setting does not sync across browsers.</span>
|
||||
<span class="fade"
|
||||
>Notifications require you to keep {{ config.name }}
|
||||
open in your browser for real-time updates. This setting
|
||||
does not sync across browsers.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
trigger("me::notifications_button", [document.getElementById("notifications_button")]);
|
||||
}, 150)
|
||||
trigger("me::notifications_button", [
|
||||
document.getElementById("notifications_button"),
|
||||
]);
|
||||
}, 150);
|
||||
</script>
|
||||
|
||||
<div class="card-nest" ui_ident="change_password">
|
||||
<div class="card small">
|
||||
<b>{{ text "settings:label.change_password" }}</b>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="card flex flex-col gap-2"
|
||||
onsubmit="change_password(event)"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="current_password"
|
||||
>{{ text "settings:label.current_password" }}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
name="current_password"
|
||||
id="current_password"
|
||||
placeholder="current_password"
|
||||
required
|
||||
minlength="6"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="new_password"
|
||||
>{{ text "settings:label.new_password" }}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
name="new_password"
|
||||
id="new_password"
|
||||
placeholder="new_password"
|
||||
required
|
||||
minlength="6"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="primary">
|
||||
{{ icon "check" }}
|
||||
<span>{{ text "general:action.save" }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card-nest" ui_ident="change_username">
|
||||
<div class="card small">
|
||||
<b>{{ text "settings:label.change_username" }}</b>
|
||||
|
@ -238,63 +186,9 @@
|
|||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card-nest" ui_ident="two_factor_authentication">
|
||||
<div class="card small">
|
||||
<b>{{ text "settings:label.two_factor_authentication" }}</b>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
{% if profile.totp|length == 0 %}
|
||||
<div id="totp_stuff" style="display: none">
|
||||
<span
|
||||
>Scan this QR code in a TOTP authenticator app (like
|
||||
Google Authenticator):
|
||||
</span>
|
||||
|
||||
<img id="totp_qr" style="max-width: 250px" />
|
||||
|
||||
<span>TOTP secret (do NOT share):</span>
|
||||
<pre id="totp_secret"></pre>
|
||||
|
||||
<span
|
||||
>Recovery codes (STORE SAFELY, these can only be
|
||||
viewed once):</span
|
||||
>
|
||||
|
||||
<pre id="totp_recovery_codes"></pre>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="quaternary green"
|
||||
onclick="enable_totp(event)"
|
||||
>
|
||||
Enable TOTP 2FA
|
||||
</button>
|
||||
{% else %}
|
||||
<pre id="totp_recovery_codes" style="display: none"></pre>
|
||||
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button
|
||||
class="quaternary red"
|
||||
onclick="refresh_totp_codes(event)"
|
||||
>
|
||||
Refresh recovery codes
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="quaternary red"
|
||||
onclick="disable_totp(event)"
|
||||
>
|
||||
Disable TOTP 2FA
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-nest" ui_ident="change_password">
|
||||
<div class="card-nest" ui_ident="delete_account">
|
||||
<div class="card small flex items-center gap-2 red">
|
||||
{{ icon "skull" }}
|
||||
<b>{{ text "settings:label.delete_account" }}</b>
|
||||
|
@ -332,8 +226,262 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-2" data-tab="account/security">
|
||||
<div class="card tertiary flex flex-col gap-2">
|
||||
<a href="#/account" class="button secondary">
|
||||
{{ icon "arrow-left" }}
|
||||
<span>{{ text "general:action.back" }}</span>
|
||||
</a>
|
||||
|
||||
<div class="card-nest">
|
||||
<div class="card flex items-center gap-2 small">
|
||||
{{ icon "user-lock" }}
|
||||
<span>{{ text "settings:tab.security" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2 secondary">
|
||||
<div class="card-nest" ui_ident="two_factor_authentication">
|
||||
<div class="card small">
|
||||
<b
|
||||
>{{ text
|
||||
"settings:label.two_factor_authentication" }}</b
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
{% if profile.totp|length == 0 %}
|
||||
<div id="totp_stuff" style="display: none">
|
||||
<span
|
||||
>Scan this QR code in a TOTP authenticator
|
||||
app (like Google Authenticator):
|
||||
</span>
|
||||
|
||||
<img id="totp_qr" style="max-width: 250px" />
|
||||
|
||||
<span>TOTP secret (do NOT share):</span>
|
||||
<pre id="totp_secret"></pre>
|
||||
|
||||
<span
|
||||
>Recovery codes (STORE SAFELY, these can
|
||||
only be viewed once):</span
|
||||
>
|
||||
|
||||
<pre id="totp_recovery_codes"></pre>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="quaternary green"
|
||||
onclick="enable_totp(event)"
|
||||
>
|
||||
Enable TOTP 2FA
|
||||
</button>
|
||||
{% else %}
|
||||
<pre
|
||||
id="totp_recovery_codes"
|
||||
style="display: none"
|
||||
></pre>
|
||||
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button
|
||||
class="quaternary red"
|
||||
onclick="refresh_totp_codes(event)"
|
||||
>
|
||||
Refresh recovery codes
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="quaternary red"
|
||||
onclick="disable_totp(event)"
|
||||
>
|
||||
Disable TOTP 2FA
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-nest" ui_ident="change_password">
|
||||
<div class="card small">
|
||||
<b>{{ text "settings:label.change_password" }}</b>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="card flex flex-col gap-2"
|
||||
onsubmit="change_password(event)"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="current_password"
|
||||
>{{ text "settings:label.current_password"
|
||||
}}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
name="current_password"
|
||||
id="current_password"
|
||||
placeholder="current_password"
|
||||
required
|
||||
minlength="6"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="new_password"
|
||||
>{{ text "settings:label.new_password"
|
||||
}}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
name="new_password"
|
||||
id="new_password"
|
||||
placeholder="new_password"
|
||||
required
|
||||
minlength="6"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="primary">
|
||||
{{ icon "check" }}
|
||||
<span>{{ text "general:action.save" }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-2" data-tab="account/blocks">
|
||||
<div class="card tertiary flex flex-col gap-2">
|
||||
<a href="#/account" class="button secondary">
|
||||
{{ icon "arrow-left" }}
|
||||
<span>{{ text "general:action.back" }}</span>
|
||||
</a>
|
||||
|
||||
<div class="card-nest">
|
||||
<div class="card flex items-center gap-2 small">
|
||||
{{ icon "users-round" }}
|
||||
<span>{{ text "settings:label.users" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
{% for user in blocks %}
|
||||
<div
|
||||
class="card secondary flex flex-wrap gap-2 items-center justify-between"
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
{{ components::avatar(username=user.username) }} {{
|
||||
components::full_username(user=user) }}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/@{{ user.username }}"
|
||||
class="button quaternary small"
|
||||
>
|
||||
{{ icon "external-link" }}
|
||||
<span
|
||||
>{{ text "requests:action.view_profile" }}</span
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-2" data-tab="account/billing">
|
||||
<div class="card tertiary flex flex-col gap-2">
|
||||
<a href="#/account" class="button secondary">
|
||||
{{ icon "arrow-left" }}
|
||||
<span>{{ text "general:action.back" }}</span>
|
||||
</a>
|
||||
|
||||
<div class="card-nest">
|
||||
<div class="card flex items-center gap-2 small">
|
||||
{{ icon "credit-card" }}
|
||||
<span>{{ text "settings:tab.billing" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2 secondary">
|
||||
{% if config.stripe %}
|
||||
<div class="card-nest" ui_ident="supporter_card">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "star" }}
|
||||
<b>Supporter status</b>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
{% if is_supporter %}
|
||||
<p>
|
||||
You <b>are</b> a supporter! Thank you for all
|
||||
that you do. You can manage your billing
|
||||
information below.
|
||||
<b
|
||||
>Please use your email address you supplied
|
||||
when paying to login to the billing
|
||||
portal.</b
|
||||
>
|
||||
</p>
|
||||
<a
|
||||
href="{{ config.stripe.billing_portal_url }}"
|
||||
class="button quaternary"
|
||||
target="_blank"
|
||||
>Manage billing</a
|
||||
>
|
||||
{% else %}
|
||||
<p>
|
||||
You're <b>not</b> currently a supporter! No
|
||||
pressure, but it helps us do some pretty cool
|
||||
things! As a supporter, you'll get:
|
||||
</p>
|
||||
|
||||
<ul style="margin-bottom: 1rem">
|
||||
<li>Vanity badge on profile</li>
|
||||
<li>No more supporter ads (duh)</li>
|
||||
<li>Ability to upload gif avatars/banners</li>
|
||||
<li>
|
||||
Be an admin/owner of up to 10 communities
|
||||
</li>
|
||||
<li>Use custom CSS on your profile</li>
|
||||
<li>
|
||||
Ability to use community emojis outside of
|
||||
their community <b>(soon)</b>
|
||||
</li>
|
||||
<li>
|
||||
Ability to upload and use gif emojis
|
||||
<b>(soon)</b>
|
||||
</li>
|
||||
<li>Create infinite stack timelines</li>
|
||||
</ul>
|
||||
|
||||
<a
|
||||
href="{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}"
|
||||
class="button"
|
||||
target="_blank"
|
||||
>Become a supporter</a
|
||||
>
|
||||
|
||||
<span class="fade"
|
||||
>Please use your <b>real email</b> when
|
||||
completing payment. It is required to manage
|
||||
your billing settings.</span
|
||||
>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full hidden flex flex-col gap-2" data-tab="profile">
|
||||
<div class="card tertiary flex flex-col gap-2" id="profile_settings">
|
||||
{{ components::supporter_ad(body="Become a supporter to upload GIF
|
||||
images!") }}
|
||||
|
||||
<div class="card-nest" ui_ident="change_avatar">
|
||||
<div class="card small">
|
||||
<b>{{ text "settings:label.change_avatar" }}</b>
|
||||
|
@ -358,7 +506,9 @@
|
|||
</div>
|
||||
|
||||
<span class="fade"
|
||||
>Images must be less than 8 MB large. Animated GIFs are only supported for supporter users. GIFs can be at most 2 MB large.</span
|
||||
>Images must be less than 8 MB large. Animated GIFs are
|
||||
only supported for supporter users. GIFs can be at most
|
||||
2 MB large.</span
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -477,6 +627,9 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{{ components::supporter_ad(body="Become a supporter to add custom
|
||||
CSS!") }}
|
||||
|
||||
<div class="card-nest" ui_ident="theme_preference">
|
||||
<div class="card small">
|
||||
<b>Theme preference</b>
|
||||
|
@ -607,9 +760,9 @@
|
|||
</button>
|
||||
|
||||
<label for="{{ key }}-shown" class="flex items-center gap-2">
|
||||
<!-- prettier-ignore -->
|
||||
<input
|
||||
type="checkbox"
|
||||
<!-- prettier-ignore -->
|
||||
{% if value[0].show_on_profile %}checked{% endif %}
|
||||
id="{{ key }}-shown"
|
||||
onchange="trigger('connections::push_con_shown', ['{{ key }}', event.target.checked])"
|
||||
|
@ -933,18 +1086,20 @@
|
|||
const theme_settings = document.getElementById("theme_settings");
|
||||
|
||||
ui.refresh_container(account_settings, [
|
||||
"supporter_card",
|
||||
"supporter_ad",
|
||||
"account_settings_tabs",
|
||||
"home_timeline",
|
||||
"notifications",
|
||||
"change_password",
|
||||
"change_username",
|
||||
"two_factor_authentication",
|
||||
"delete_account",
|
||||
]);
|
||||
ui.refresh_container(profile_settings, [
|
||||
"supporter_ad",
|
||||
"change_avatar",
|
||||
"change_banner",
|
||||
]);
|
||||
ui.refresh_container(theme_settings, [
|
||||
"supporter_ad",
|
||||
"awful_contrast",
|
||||
"import_export",
|
||||
"theme_preference",
|
||||
|
@ -964,11 +1119,7 @@
|
|||
settings.biography,
|
||||
"textarea",
|
||||
],
|
||||
[
|
||||
["status", "Status"],
|
||||
settings.status,
|
||||
"textarea",
|
||||
],
|
||||
[["status", "Status"], settings.status, "textarea"],
|
||||
[
|
||||
["warning", "Profile warning"],
|
||||
settings.warning,
|
||||
|
|
|
@ -23,7 +23,7 @@ pub async fn stripe_webhook(
|
|||
|
||||
let req = match stripe::Webhook::construct_event(
|
||||
&body,
|
||||
&sig.to_str().unwrap(),
|
||||
sig.to_str().unwrap(),
|
||||
&data.0.stripe.as_ref().unwrap().webhook_signing_secret,
|
||||
) {
|
||||
Ok(e) => e,
|
||||
|
|
|
@ -216,15 +216,13 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
|
|||
if !cs {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if !headers.is_channel {
|
||||
// since we didn't select by just a channel, there HAS to be
|
||||
// an entry for the channel for us to check this message
|
||||
continue;
|
||||
// we don't need to check messages when we're subscribed to
|
||||
// a channel, since that is checked on headers submission when
|
||||
// we subscribe to a channel
|
||||
}
|
||||
} else if !headers.is_channel {
|
||||
// since we didn't select by just a channel, there HAS to be
|
||||
// an entry for the channel for us to check this message
|
||||
continue;
|
||||
// we don't need to check messages when we're subscribed to
|
||||
// a channel, since that is checked on headers submission when
|
||||
// we subscribe to a channel
|
||||
}
|
||||
|
||||
if sink.send(WsMessage::Text(smsg.into())).await.is_err() {
|
||||
|
|
|
@ -67,7 +67,7 @@ pub async fn app_request(
|
|||
Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)),
|
||||
};
|
||||
|
||||
if let Some(ref channel) = channels.get(0) {
|
||||
if let Some(channel) = channels.first() {
|
||||
return Ok(Html(format!(
|
||||
"<!doctype html><html><head><meta http-equiv=\"refresh\" content=\"0; url=/chats/{}/{}?nav={}\" /></head></html>",
|
||||
selected_community, channel.id, props.nav
|
||||
|
|
|
@ -56,6 +56,15 @@ pub async fn settings_request(
|
|||
}
|
||||
};
|
||||
|
||||
let blocks = match data
|
||||
.0
|
||||
.fill_userblocks_receivers(data.0.get_userblocks_by_initiator(profile.id).await)
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(e) => return Err(Html(render_error(e, &jar, &data, &None).await)),
|
||||
};
|
||||
|
||||
let tokens = profile.tokens.clone();
|
||||
|
||||
let lang = get_lang!(jar, data.0);
|
||||
|
@ -63,6 +72,7 @@ pub async fn settings_request(
|
|||
|
||||
context.insert("profile", &profile);
|
||||
context.insert("stacks", &stacks);
|
||||
context.insert("blocks", &blocks);
|
||||
context.insert("user_settings_serde", &clean_settings(&profile.settings));
|
||||
context.insert(
|
||||
"user_tokens_serde",
|
||||
|
|
|
@ -23,7 +23,7 @@ async-recursion = "1.1.1"
|
|||
md-5 = "0.10.6"
|
||||
base16ct = { version = "0.2.0", features = ["alloc"] }
|
||||
|
||||
redis = { version = "0.30.0", features = [
|
||||
redis = { version = "0.31.0", features = [
|
||||
"aio",
|
||||
"tokio-comp",
|
||||
], optional = true }
|
||||
|
|
|
@ -43,7 +43,7 @@ impl DataManager {
|
|||
let mut out = Vec::new();
|
||||
|
||||
for member in members {
|
||||
if ignore_users.contains(&member) {
|
||||
if ignore_users.contains(member) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ impl DataManager {
|
|||
let mut users: HashMap<usize, User> = HashMap::new();
|
||||
for (i, message) in messages.iter().enumerate() {
|
||||
let next_owner: usize = match messages.get(i + 1) {
|
||||
Some(ref m) => m.owner,
|
||||
Some(m) => m.owner,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
|
|
|
@ -171,10 +171,8 @@ impl DataManager {
|
|||
let stack = self.get_stack_by_id(id).await?;
|
||||
|
||||
// check user permission
|
||||
if user.id != stack.owner {
|
||||
if !user.permissions.check(FinePermission::MANAGE_STACKS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
if user.id != stack.owner && !user.permissions.check(FinePermission::MANAGE_STACKS) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
// ...
|
||||
|
|
|
@ -25,6 +25,17 @@ impl DataManager {
|
|||
|
||||
auto_method!(get_userblock_by_id()@get_userblock_from_row -> "SELECT * FROM userblocks WHERE id = $1" --name="user block" --returns=UserBlock --cache-key-tmpl="atto.userblock:{}");
|
||||
|
||||
/// Fill a vector of user blocks with their receivers.
|
||||
pub async fn fill_userblocks_receivers(&self, list: Vec<UserBlock>) -> Result<Vec<User>> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
for block in list {
|
||||
out.push(self.get_user_by_id(block.receiver).await?);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Get a user block by `initiator` and `receiver` (in that order).
|
||||
pub async fn get_userblock_by_initiator_receiver(
|
||||
&self,
|
||||
|
@ -104,6 +115,28 @@ impl DataManager {
|
|||
out
|
||||
}
|
||||
|
||||
/// Get all user blocks created by the given `initiator`.
|
||||
pub async fn get_userblocks_by_initiator(&self, initiator: usize) -> Vec<UserBlock> {
|
||||
let conn = match self.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM userblocks WHERE initiator = $1",
|
||||
&[&(initiator as i64)],
|
||||
|x| { Self::get_userblock_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// return
|
||||
res.unwrap()
|
||||
}
|
||||
|
||||
/// Create a new user block in the database.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
|
@ -111,22 +111,21 @@ impl CustomEmoji {
|
|||
|
||||
let mut chars = input.chars();
|
||||
while let Some(char) = chars.next() {
|
||||
if char == '\\' && escape == false {
|
||||
if char == '\\' && !escape {
|
||||
escape = true;
|
||||
continue;
|
||||
} else if char == ':' && escape == false {
|
||||
} else if char == ':' && !escape {
|
||||
let mut community_id: String = String::new();
|
||||
let mut accepting_community_id_chars: bool = true;
|
||||
let mut emoji_name: String = String::new();
|
||||
|
||||
let mut char_count: u32 = 0; // if we're past the first 4 characters and we haven't hit a digit, stop accepting community id
|
||||
while let Some(char) = chars.next() {
|
||||
for (char_count, char) in (0_u32..).zip(chars.by_ref()) {
|
||||
if (char == ':') | (char == ' ') {
|
||||
in_emoji = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if char.is_digit(10) && accepting_community_id_chars {
|
||||
if char.is_ascii_digit() && accepting_community_id_chars {
|
||||
community_id.push(char);
|
||||
} else if char == '.' {
|
||||
// the period closes the community id
|
||||
|
@ -138,8 +137,6 @@ impl CustomEmoji {
|
|||
if char_count >= 4 && community_id.is_empty() {
|
||||
accepting_community_id_chars = false;
|
||||
}
|
||||
|
||||
char_count += 1;
|
||||
}
|
||||
|
||||
out.push((
|
||||
|
|
|
@ -7,6 +7,12 @@ pub const EPOCH_2024: u64 = 1704067200000;
|
|||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Snowflake(String);
|
||||
|
||||
impl Default for Snowflake {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Snowflake {
|
||||
pub fn builder() -> Builder {
|
||||
Builder::new().epoch(UNIX_EPOCH + Duration::from_millis(EPOCH_2024))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue