mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 12:05:53 +00:00
menu refactor + web account switcher (#7307)
* add account switcher, migrate context menus to overflow menu system, update version filter controls, only pad instance icons * app account switcher, improve some context menus, theme stuff * prepr * handle reauthentication * fix a couple sign in issues, admin page cleanup, fix org status badges, fix official account badge, open in local keybind, publish plus icons * rename generic menus to ButtonMenu (& types) * fix hydration issue w/ dropdowns + middleware error
This commit is contained in:
Generated
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id, active, session_id, expires\n FROM modrinth_users\n ORDER BY rowid\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "active",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "session_id",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "expires",
|
||||
"ordinal": 3,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c7ade4d5f2b4b910b8d3a100d7285ac08744fc65c686cc185c1af97ea7f0738a"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::state::ModrinthCredentials;
|
||||
use crate::state::{FeatureFlag, ModrinthCredentials, Settings};
|
||||
use serde::Deserialize;
|
||||
|
||||
const LOCALHOST_LOGIN_URL: &str = "http://localhost:3000/auth/sign-in";
|
||||
const LOCALHOST_SIGNUP_URL: &str = "http://localhost:3000/auth/sign-up";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ModrinthAuthFlow {
|
||||
@@ -9,11 +12,23 @@ pub enum ModrinthAuthFlow {
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn authenticate_begin_flow(flow: ModrinthAuthFlow) -> &'static str {
|
||||
match flow {
|
||||
ModrinthAuthFlow::SignIn => crate::state::get_login_url(),
|
||||
ModrinthAuthFlow::SignUp => crate::state::get_signup_url(),
|
||||
}
|
||||
pub async fn authenticate_begin_flow(
|
||||
flow: ModrinthAuthFlow,
|
||||
) -> crate::Result<&'static str> {
|
||||
let state = crate::State::get().await?;
|
||||
let settings = Settings::get(&state.pool).await?;
|
||||
let use_localhost = settings
|
||||
.feature_flags
|
||||
.get(&FeatureFlag::LocalhostSignIn)
|
||||
.copied()
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(match (use_localhost, flow) {
|
||||
(true, ModrinthAuthFlow::SignIn) => LOCALHOST_LOGIN_URL,
|
||||
(true, ModrinthAuthFlow::SignUp) => LOCALHOST_SIGNUP_URL,
|
||||
(false, ModrinthAuthFlow::SignIn) => crate::state::get_login_url(),
|
||||
(false, ModrinthAuthFlow::SignUp) => crate::state::get_signup_url(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
@@ -39,11 +54,7 @@ pub async fn authenticate_finish_flow(
|
||||
);
|
||||
}
|
||||
|
||||
state.friends_socket.disconnect().await?;
|
||||
state
|
||||
.friends_socket
|
||||
.connect(&state.pool, &state.api_semaphore, &state.process_manager)
|
||||
.await?;
|
||||
reconnect_friends(&state).await?;
|
||||
|
||||
Ok(creds)
|
||||
}
|
||||
@@ -51,16 +62,66 @@ pub async fn authenticate_finish_flow(
|
||||
#[tracing::instrument]
|
||||
pub async fn logout() -> crate::Result<()> {
|
||||
let state = crate::State::get().await?;
|
||||
let current = ModrinthCredentials::get_active(&state.pool).await?;
|
||||
|
||||
if let Some(current) = current {
|
||||
ModrinthCredentials::remove(¤t.user_id, &state.pool).await?;
|
||||
}
|
||||
ModrinthCredentials::deactivate_all(&state.pool).await?;
|
||||
state.friends_socket.disconnect().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_all() -> crate::Result<Vec<ModrinthCredentials>> {
|
||||
let state = crate::State::get().await?;
|
||||
ModrinthCredentials::get_all(&state.pool).await
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn set_active(user_id: &str) -> crate::Result<()> {
|
||||
let state = crate::State::get().await?;
|
||||
let users = ModrinthCredentials::get_all(&state.pool).await?;
|
||||
let Some(mut creds) =
|
||||
users.into_iter().find(|creds| creds.user_id == user_id)
|
||||
else {
|
||||
return Err(crate::ErrorKind::OtherError(format!(
|
||||
"Tried to activate nonexistent Modrinth user with ID {user_id}"
|
||||
))
|
||||
.as_error());
|
||||
};
|
||||
|
||||
creds.active = true;
|
||||
creds.upsert(&state.pool).await?;
|
||||
reconnect_friends(&state).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn remove_user(user_id: &str) -> crate::Result<()> {
|
||||
let state = crate::State::get().await?;
|
||||
let current = ModrinthCredentials::get_active(&state.pool).await?;
|
||||
ModrinthCredentials::remove(user_id, &state.pool).await?;
|
||||
|
||||
if current.is_some_and(|creds| creds.user_id == user_id) {
|
||||
state.friends_socket.disconnect().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reconnect_friends(state: &crate::State) -> crate::Result<()> {
|
||||
if let Err(error) = state.friends_socket.disconnect().await {
|
||||
tracing::warn!("Failed to disconnect friends socket: {error}");
|
||||
}
|
||||
if let Err(error) = state
|
||||
.friends_socket
|
||||
.connect(&state.pool, &state.api_semaphore, &state.process_manager)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("Failed to reconnect friends socket: {error}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_credentials() -> crate::Result<Option<ModrinthCredentials>> {
|
||||
let state = crate::State::get().await?;
|
||||
|
||||
@@ -591,11 +591,7 @@ async fn censor_support_text(
|
||||
mut text: String,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
for credentials in ModrinthCredentials::get_all(&state.pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|credentials| credentials.1)
|
||||
{
|
||||
for credentials in ModrinthCredentials::get_all(&state.pool).await? {
|
||||
replace_nonempty(
|
||||
&mut text,
|
||||
&credentials.session,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::state::{CacheBehaviour, CachedEntry};
|
||||
use crate::util::fetch::{FetchSemaphore, fetch_advanced};
|
||||
use chrono::{DateTime, Duration, TimeZone, Utc};
|
||||
use dashmap::DashMap;
|
||||
use futures::TryStreamExt;
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -90,34 +88,30 @@ impl ModrinthCredentials {
|
||||
|
||||
pub async fn get_all(
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<DashMap<String, Self>> {
|
||||
) -> crate::Result<Vec<Self>> {
|
||||
let res = sqlx::query!(
|
||||
"
|
||||
SELECT
|
||||
id, active, session_id, expires
|
||||
FROM modrinth_users
|
||||
ORDER BY rowid
|
||||
"
|
||||
)
|
||||
.fetch(exec)
|
||||
.try_fold(DashMap::new(), |acc, x| {
|
||||
acc.insert(
|
||||
x.id.clone(),
|
||||
Self {
|
||||
session: x.session_id,
|
||||
expires: Utc
|
||||
.timestamp_opt(x.expires, 0)
|
||||
.single()
|
||||
.unwrap_or_else(Utc::now),
|
||||
user_id: x.id,
|
||||
active: x.active == 1,
|
||||
},
|
||||
);
|
||||
|
||||
async move { Ok(acc) }
|
||||
})
|
||||
.fetch_all(exec)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
Ok(res
|
||||
.into_iter()
|
||||
.map(|x| Self {
|
||||
session: x.session_id,
|
||||
expires: Utc
|
||||
.timestamp_opt(x.expires, 0)
|
||||
.single()
|
||||
.unwrap_or_else(Utc::now),
|
||||
user_id: x.id,
|
||||
active: x.active == 1,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn upsert(
|
||||
@@ -127,14 +121,7 @@ impl ModrinthCredentials {
|
||||
let expires = self.expires.timestamp();
|
||||
|
||||
if self.active {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE modrinth_users
|
||||
SET active = FALSE
|
||||
"
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
Self::deactivate_all(exec).await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
@@ -157,6 +144,21 @@ impl ModrinthCredentials {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn deactivate_all(
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<()> {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE modrinth_users
|
||||
SET active = FALSE
|
||||
"
|
||||
)
|
||||
.execute(exec)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove(
|
||||
user_id: &str,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
@@ -177,7 +179,7 @@ impl ModrinthCredentials {
|
||||
let state = crate::State::get().await?;
|
||||
let all = Self::get_all(&state.pool).await?;
|
||||
|
||||
let user_ids = all.into_iter().map(|x| x.0).collect::<Vec<_>>();
|
||||
let user_ids = all.into_iter().map(|x| x.user_id).collect::<Vec<_>>();
|
||||
|
||||
CachedEntry::get_user_many(
|
||||
&user_ids.iter().map(|x| &**x).collect::<Vec<_>>(),
|
||||
|
||||
@@ -72,6 +72,7 @@ pub enum FeatureFlag {
|
||||
FriendsOfflineCollapsed,
|
||||
FriendsPendingCollapsed,
|
||||
DismissedPhotosensitivityFilterWarning,
|
||||
LocalhostSignIn,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
|
||||
Reference in New Issue
Block a user