mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 17:14:50 +00:00
feat: instance sharing thru shared-instances service (#6569)
* feat: implement instance share page + search_users backend call * feat: invite players modal * feat: use tanstack queries for friends sync across app pages * feat: base shared instances implementation * fix: admon style * feat: impl instance admonitions like server panel * fix: impl get + del usage * feat: support modpack links * feat: invite notif accepting * fix: lint + fmt * feat: impl install to play * feat: impl usage of UpdateToPlayModal * feat: warnings on deleting/disabling shared-instance version content * fix: send instance name * feat: align with backend * feat: shared instances qa * feat: wrong account protection * feat: qa * fix: smartly apply updates * fix: install bug * fix: 401/404 differentiation * fix: fmt+prepr * feat: qa * feat: qa * fix: signing out messes up revoke/deleted checks * feat: qa * fix: fmt + lint * feat: lock content if part of shared instance * fix: lint * [do not merge] feat: rough invite links impl temp (#6666) * fix: wrong cmd * feat: invite page * fix: server-manager DTO mismatch * fix: drop anonymous invite link acceptance * refactor: structured shared-instance unavailable errors * refactor: centralise error presentations * refactor: dedupe shared instance diff detection * fix: logging in reqwests * refactor: move app.vue shared instances into handler * refactor: break up Share.vue * refactor: split up shared instances state outside of instance index * refactor: dedicated shared instances install/update modals + split up page * refactor: centralized managed content * refactor: split up install shared to own runner + shared.rs split up * refactor: dedupe sql for instance metadata enrichmnt * refactor: friends composable + dedupe friends logic across usages * chore: reduced unused code * fix: align with backend * fix: lint * fix: file sha changes * fix: invite links not working due to icon signed * feat: qa * feat: reporting frontend dummy * fix: try use header * remove: file hash field * fix: pin box * feat: malware warning for shared instances * fix: cache rule * feat: config files syncing * feat: disable config sharing * fix: header * fix: use mark ready * fix: dont cause push update for configs * fix: lint * feat: sharing page in settings * feat: move config + change flow * fix: qa * fix: lint prepr * feat: proxy file upload thru shared instances backend * fix: use collapisible * fix: push config * fix: config * feat: swap out sign in modal for new one * fix: report flow * fix: exclude configs.zip from external warnings * fix: nuxi init * fix: config bundle downloading * fix: error notif * fix: polling * fix: qa * fix: lint + prepr * feat: shared instances moderation frontend + hook up report flow * fix: report copy * fix: lint * fix: lint * fix: modrinth ids being undefined * feat: instance quarantining * fix: prepr + fmt * fix: quarantined -> locked terminology * fix: missing endpoint impls + fmt * fix: missing api in build.rs * fix: share tab jittery * fix: fmt *PT bug * fix: invites count as users even if pending * fix: prepr * fix: invite page owner in users list * fix: lint * fix: qa * fix: lint * fix: members stale not clearing * fix: invite use joined_at field * fix: lint * fix: qa --------- Co-authored-by: sychic <47618543+Sychic@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,10 @@ use theseus::data::ModLoader;
|
||||
use theseus::install::{
|
||||
InstallJobSnapshot, InstallModpackPreview, InstallPostInstallEdit,
|
||||
};
|
||||
use theseus::instance::{
|
||||
SharedInstanceInstallPreview, SharedInstanceInviteInstallPreview,
|
||||
SharedInstanceUpdatePreview,
|
||||
};
|
||||
use theseus::pack::import::ImportLauncherType;
|
||||
use theseus::pack::install_from::CreatePackLocation;
|
||||
use uuid::Uuid;
|
||||
@@ -16,6 +20,11 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
install_get_modpack_preview,
|
||||
install_create_instance,
|
||||
install_create_modpack_instance,
|
||||
install_get_shared_instance_preview,
|
||||
install_accept_shared_instance_invite,
|
||||
install_get_shared_instance_update_preview,
|
||||
install_shared_instance,
|
||||
install_update_shared_instance,
|
||||
install_import_instance,
|
||||
install_duplicate_instance,
|
||||
install_existing_instance,
|
||||
@@ -101,6 +110,67 @@ pub async fn install_create_modpack_instance(
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_get_shared_instance_preview(
|
||||
shared_instance_id: String,
|
||||
name: String,
|
||||
) -> Result<SharedInstanceInstallPreview> {
|
||||
Ok(theseus::instance::get_shared_instance_install_preview(
|
||||
&shared_instance_id,
|
||||
name,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_accept_shared_instance_invite(
|
||||
invite_id: String,
|
||||
) -> Result<SharedInstanceInviteInstallPreview> {
|
||||
Ok(
|
||||
theseus::instance::accept_shared_instance_invite_for_install(
|
||||
&invite_id,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_get_shared_instance_update_preview(
|
||||
instance_id: String,
|
||||
) -> Result<Option<SharedInstanceUpdatePreview>> {
|
||||
Ok(
|
||||
theseus::instance::get_shared_instance_update_preview(&instance_id)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_shared_instance(
|
||||
shared_instance_id: String,
|
||||
name: String,
|
||||
manager_id: Option<String>,
|
||||
server_manager_name: Option<String>,
|
||||
server_manager_icon_url: Option<String>,
|
||||
instance_icon_url: Option<String>,
|
||||
) -> Result<InstallJobSnapshot> {
|
||||
Ok(theseus::instance::install_shared_instance(
|
||||
&shared_instance_id,
|
||||
name,
|
||||
manager_id,
|
||||
server_manager_name,
|
||||
server_manager_icon_url,
|
||||
instance_icon_url,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_update_shared_instance(
|
||||
instance_id: String,
|
||||
) -> Result<InstallJobSnapshot> {
|
||||
Ok(theseus::instance::update_shared_instance(&instance_id).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_import_instance(
|
||||
launcher_type: ImportLauncherType,
|
||||
|
||||
+147
-19
@@ -10,6 +10,8 @@ use theseus::data::{
|
||||
EditInstance as CoreEditInstance, InstanceInstallCandidate,
|
||||
InstanceInstallTarget, InstanceLaunchOverridesPatch,
|
||||
InstanceLink as CoreInstanceLink, InstanceMetadata, LinkedModpackInfo,
|
||||
SharedInstanceAttachment as CoreSharedInstanceAttachment,
|
||||
SharedInstanceRole,
|
||||
};
|
||||
use theseus::instance::InstallProjectWithDependenciesRequest;
|
||||
use theseus::instance::QuickPlayType;
|
||||
@@ -50,6 +52,15 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
instance_kill,
|
||||
instance_edit,
|
||||
instance_edit_icon,
|
||||
instance_share_can_current_user_use,
|
||||
instance_share_get_users,
|
||||
instance_share_invite_users,
|
||||
instance_share_create_invite_link,
|
||||
instance_share_remove_users,
|
||||
instance_share_get_publish_preview,
|
||||
instance_share_publish,
|
||||
instance_share_unlink,
|
||||
instance_share_unpublish,
|
||||
instance_export_mrpack,
|
||||
instance_get_pack_export_candidates,
|
||||
])
|
||||
@@ -70,6 +81,8 @@ pub struct Instance {
|
||||
pub loader_version: Option<String>,
|
||||
pub groups: Vec<String>,
|
||||
pub link: Option<InstanceLink>,
|
||||
pub shared_instance: Option<SharedInstanceAttachment>,
|
||||
pub quarantined: bool,
|
||||
pub update_channel: ReleaseChannel,
|
||||
pub created: chrono::DateTime<chrono::Utc>,
|
||||
pub modified: chrono::DateTime<chrono::Utc>,
|
||||
@@ -115,10 +128,40 @@ pub enum InstanceLink {
|
||||
active_instance_id: Option<String>,
|
||||
},
|
||||
SharedInstance {
|
||||
shared_instance_id: String,
|
||||
modpack_project_id: Option<String>,
|
||||
modpack_version_id: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct SharedInstanceAttachment {
|
||||
pub id: String,
|
||||
pub role: SharedInstanceRole,
|
||||
pub manager_id: Option<String>,
|
||||
pub server_manager_name: Option<String>,
|
||||
pub server_manager_icon_url: Option<String>,
|
||||
pub linked_user_id: Option<String>,
|
||||
pub status: String,
|
||||
pub applied_version: Option<i32>,
|
||||
pub latest_version: Option<i32>,
|
||||
}
|
||||
|
||||
impl From<CoreSharedInstanceAttachment> for SharedInstanceAttachment {
|
||||
fn from(attachment: CoreSharedInstanceAttachment) -> Self {
|
||||
Self {
|
||||
id: attachment.id.to_string(),
|
||||
role: attachment.role,
|
||||
manager_id: attachment.manager_id,
|
||||
server_manager_name: attachment.server_manager_name,
|
||||
server_manager_icon_url: attachment.server_manager_icon_url,
|
||||
linked_user_id: attachment.linked_user_id,
|
||||
status: attachment.status.as_str().to_string(),
|
||||
applied_version: attachment.applied_version,
|
||||
latest_version: attachment.latest_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct EditInstance {
|
||||
pub name: Option<String>,
|
||||
@@ -201,6 +244,8 @@ impl From<InstanceMetadata> for Instance {
|
||||
loader_version: metadata.applied_content_set.loader_version,
|
||||
groups: metadata.groups,
|
||||
link: InstanceLink::from_core(metadata.link),
|
||||
shared_instance: metadata.shared_instance.map(Into::into),
|
||||
quarantined: metadata.quarantined,
|
||||
update_channel: metadata.instance.update_channel,
|
||||
created: metadata.instance.created,
|
||||
modified: metadata.instance.modified,
|
||||
@@ -268,11 +313,13 @@ impl InstanceLink {
|
||||
.collect(),
|
||||
active_instance_id: active_instance_id.map(|id| id.to_string()),
|
||||
}),
|
||||
CoreInstanceLink::SharedInstance { shared_instance_id } => {
|
||||
Some(Self::SharedInstance {
|
||||
shared_instance_id: shared_instance_id.to_string(),
|
||||
})
|
||||
}
|
||||
CoreInstanceLink::SharedInstance {
|
||||
modpack_project_id,
|
||||
modpack_version_id,
|
||||
} => Some(Self::SharedInstance {
|
||||
modpack_project_id,
|
||||
modpack_version_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,19 +392,13 @@ impl InstanceLink {
|
||||
})
|
||||
.transpose()?,
|
||||
}),
|
||||
Self::SharedInstance { shared_instance_id } => {
|
||||
Ok(CoreInstanceLink::SharedInstance {
|
||||
shared_instance_id: shared_instance_id.parse().map_err(
|
||||
|err| {
|
||||
theseus::Error::from(
|
||||
theseus::ErrorKind::InputError(format!(
|
||||
"Invalid shared instance id: {err}"
|
||||
)),
|
||||
)
|
||||
},
|
||||
)?,
|
||||
})
|
||||
}
|
||||
Self::SharedInstance {
|
||||
modpack_project_id,
|
||||
modpack_version_id,
|
||||
} => Ok(CoreInstanceLink::SharedInstance {
|
||||
modpack_project_id,
|
||||
modpack_version_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -744,3 +785,90 @@ pub async fn instance_edit_icon(
|
||||
theseus::instance::edit_icon(instance_id, icon_path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_can_current_user_use() -> Result<bool> {
|
||||
Ok(theseus::instance::can_active_user_use_shared_instances().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_get_users(
|
||||
instance_id: &str,
|
||||
) -> Result<theseus::instance::SharedInstanceUsers> {
|
||||
Ok(theseus::instance::get_shared_instance_users(instance_id).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_invite_users(
|
||||
instance_id: &str,
|
||||
user_ids: Vec<String>,
|
||||
) -> Result<theseus::instance::SharedInstanceUsers> {
|
||||
Ok(
|
||||
theseus::instance::invite_shared_instance_users(instance_id, user_ids)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_create_invite_link(
|
||||
instance_id: &str,
|
||||
max_age_seconds: Option<i32>,
|
||||
max_uses: Option<i32>,
|
||||
replace_invite_id: Option<String>,
|
||||
) -> Result<theseus::instance::SharedInstanceInviteLink> {
|
||||
Ok(theseus::instance::create_shared_instance_invite_link(
|
||||
instance_id,
|
||||
max_age_seconds,
|
||||
max_uses,
|
||||
replace_invite_id,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_remove_users(
|
||||
instance_id: &str,
|
||||
user_ids: Vec<String>,
|
||||
has_pending_recipients: bool,
|
||||
) -> Result<theseus::instance::SharedInstanceUsers> {
|
||||
Ok(theseus::instance::remove_shared_instance_users(
|
||||
instance_id,
|
||||
user_ids,
|
||||
has_pending_recipients,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_get_publish_preview(
|
||||
instance_id: &str,
|
||||
) -> Result<Option<theseus::instance::SharedInstancePublishPreview>> {
|
||||
Ok(
|
||||
theseus::instance::get_shared_instance_publish_preview(instance_id)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_publish(
|
||||
instance_id: &str,
|
||||
config_paths: Vec<String>,
|
||||
) -> Result<SharedInstanceAttachment> {
|
||||
Ok(
|
||||
theseus::instance::publish_shared_instance(instance_id, config_paths)
|
||||
.await?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_unlink(instance_id: &str) -> Result<()> {
|
||||
theseus::instance::unlink_shared_instance(instance_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn instance_share_unpublish(instance_id: &str) -> Result<()> {
|
||||
theseus::instance::unpublish_shared_instance(instance_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+14
-1
@@ -12,9 +12,11 @@ pub mod metadata;
|
||||
pub mod minecraft_skins;
|
||||
pub mod mr_auth;
|
||||
pub mod process;
|
||||
pub mod reports;
|
||||
pub mod settings;
|
||||
pub mod shortcuts;
|
||||
pub mod tags;
|
||||
pub mod users;
|
||||
pub mod utils;
|
||||
|
||||
pub mod ads;
|
||||
@@ -85,9 +87,20 @@ macro_rules! impl_serialize {
|
||||
TheseusSerializableError::Theseus(theseus_error) => {
|
||||
$crate::error::display_tracing_error(theseus_error);
|
||||
|
||||
let mut state = serializer.serialize_struct("Theseus", 2)?;
|
||||
let unavailable_reason = match theseus_error.raw.as_ref() {
|
||||
theseus::ErrorKind::SharedInstanceUnavailable(reason) => Some(reason),
|
||||
_ => None,
|
||||
};
|
||||
let mut state = serializer.serialize_struct(
|
||||
"Theseus",
|
||||
if unavailable_reason.is_some() { 4 } else { 2 },
|
||||
)?;
|
||||
state.serialize_field("field_name", "Theseus")?;
|
||||
state.serialize_field("message", &theseus_error.to_string())?;
|
||||
if let Some(reason) = unavailable_reason {
|
||||
state.serialize_field("code", "shared_instance_unavailable")?;
|
||||
state.serialize_field("reason", reason)?;
|
||||
}
|
||||
state.end()
|
||||
}
|
||||
$(
|
||||
|
||||
@@ -22,6 +22,7 @@ pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
#[tauri::command]
|
||||
pub async fn modrinth_login<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
flow: mr_auth::ModrinthAuthFlow,
|
||||
) -> Result<ModrinthCredentials> {
|
||||
let (auth_code_recv_socket_tx, auth_code_recv_socket) = oneshot::channel();
|
||||
let auth_code = tokio::spawn(oauth_utils::auth_code_reply::listen(
|
||||
@@ -32,7 +33,7 @@ pub async fn modrinth_login<R: Runtime>(
|
||||
|
||||
let auth_request_uri = format!(
|
||||
"{}?launcher=true&ipver={}&port={}",
|
||||
mr_auth::authenticate_begin_flow(),
|
||||
mr_auth::authenticate_begin_flow(flow),
|
||||
if auth_code_recv_socket.is_ipv4() {
|
||||
"4"
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::api::Result;
|
||||
use theseus::reports::{CreateReportRequest, CreateReportResponse};
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("reports")
|
||||
.invoke_handler(tauri::generate_handler![reports_create])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reports_create(
|
||||
request: CreateReportRequest,
|
||||
) -> Result<CreateReportResponse> {
|
||||
Ok(theseus::reports::create_report(request).await?)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use crate::api::Result;
|
||||
use theseus::users::SearchUser;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn search_user(query: &str) -> Result<Vec<SearchUser>> {
|
||||
Ok(theseus::users::search_user(query).await?)
|
||||
}
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("users")
|
||||
.invoke_handler(tauri::generate_handler![search_user])
|
||||
.build()
|
||||
}
|
||||
Reference in New Issue
Block a user