Initial shared instances backend (#3800)

* Create base shared instance migration and initial routes

* Fix build

* Add version uploads

* Add permissions field for shared instance users

* Actually use permissions field

* Add "public" flag to shared instances that allow GETing them without authorization

* Add the ability to get and list shared instance versions

* Add the ability to delete shared instance versions

* Fix build after merge

* Secured file hosting (#3784)

* Remove Backblaze-specific file-hosting backend

* Added S3_USES_PATH_STYLE_BUCKETS

* Remove unused file_id parameter from delete_file_version

* Add support for separate public and private buckets in labrinth::file_hosting

* Rename delete_file_version to delete_file

* Add (untested) get_url_for_private_file

* Remove url field from shared instance routes

* Remove url field from shared instance routes

* Use private bucket for shared instance versions

* Make S3 environment variables fully separate between public and private buckets

* Change file host expiry for shared instances to 180 seconds

* Fix lint

* Merge shared instance migrations into a single migration

* Replace shared instance owners with Ghost instead of deleting the instance
This commit is contained in:
Josiah Glosson
2025-06-19 14:46:12 -05:00
committed by GitHub
parent d4864deac5
commit cc34e69524
61 changed files with 2161 additions and 491 deletions

View File

@@ -16,6 +16,7 @@ pub use v3::payouts;
pub use v3::projects;
pub use v3::reports;
pub use v3::sessions;
pub use v3::shared_instances;
pub use v3::teams;
pub use v3::threads;
pub use v3::users;

View File

@@ -17,6 +17,8 @@ base62_id!(ProductPriceId);
base62_id!(ProjectId);
base62_id!(ReportId);
base62_id!(SessionId);
base62_id!(SharedInstanceId);
base62_id!(SharedInstanceVersionId);
base62_id!(TeamId);
base62_id!(TeamMemberId);
base62_id!(ThreadId);

View File

@@ -12,6 +12,7 @@ pub mod payouts;
pub mod projects;
pub mod reports;
pub mod sessions;
pub mod shared_instances;
pub mod teams;
pub mod threads;
pub mod users;

View File

@@ -100,6 +100,24 @@ bitflags::bitflags! {
// only accessible by modrinth-issued sessions
const SESSION_ACCESS = 1 << 39;
// create a shared instance
const SHARED_INSTANCE_CREATE = 1 << 40;
// read a shared instance
const SHARED_INSTANCE_READ = 1 << 41;
// write to a shared instance
const SHARED_INSTANCE_WRITE = 1 << 42;
// delete a shared instance
const SHARED_INSTANCE_DELETE = 1 << 43;
// create a shared instance version
const SHARED_INSTANCE_VERSION_CREATE = 1 << 44;
// read a shared instance version
const SHARED_INSTANCE_VERSION_READ = 1 << 45;
// write to a shared instance version
const SHARED_INSTANCE_VERSION_WRITE = 1 << 46;
// delete a shared instance version
const SHARED_INSTANCE_VERSION_DELETE = 1 << 47;
const NONE = 0b0;
}
}

View File

@@ -0,0 +1,89 @@
use crate::bitflags_serde_impl;
use crate::database::models::shared_instance_item::{
DBSharedInstance, DBSharedInstanceUser, DBSharedInstanceVersion,
};
use crate::models::ids::{SharedInstanceId, SharedInstanceVersionId};
use ariadne::ids::UserId;
use bitflags::bitflags;
use chrono::{DateTime, Utc};
use hex::ToHex;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedInstance {
pub id: SharedInstanceId,
pub title: String,
pub owner: UserId,
pub public: bool,
pub current_version: Option<SharedInstanceVersion>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_users: Option<Vec<SharedInstanceUser>>,
}
impl SharedInstance {
pub fn from_db(
instance: DBSharedInstance,
users: Option<Vec<DBSharedInstanceUser>>,
current_version: Option<DBSharedInstanceVersion>,
) -> Self {
SharedInstance {
id: instance.id.into(),
title: instance.title,
owner: instance.owner_id.into(),
public: instance.public,
current_version: current_version.map(Into::into),
additional_users: users
.map(|x| x.into_iter().map(Into::into).collect()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedInstanceVersion {
pub id: SharedInstanceVersionId,
pub shared_instance: SharedInstanceId,
pub size: u64,
pub sha512: String,
pub created: DateTime<Utc>,
}
impl From<DBSharedInstanceVersion> for SharedInstanceVersion {
fn from(value: DBSharedInstanceVersion) -> Self {
let version_id = value.id.into();
let shared_instance_id = value.shared_instance_id.into();
SharedInstanceVersion {
id: version_id,
shared_instance: shared_instance_id,
size: value.size,
sha512: value.sha512.encode_hex(),
created: value.created,
}
}
}
bitflags! {
#[derive(Copy, Clone, Debug)]
pub struct SharedInstanceUserPermissions: u64 {
const EDIT = 1 << 0;
const DELETE = 1 << 1;
const UPLOAD_VERSION = 1 << 2;
const DELETE_VERSION = 1 << 3;
}
}
bitflags_serde_impl!(SharedInstanceUserPermissions, u64);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedInstanceUser {
pub user: UserId,
pub permissions: SharedInstanceUserPermissions,
}
impl From<DBSharedInstanceUser> for SharedInstanceUser {
fn from(user: DBSharedInstanceUser) -> Self {
SharedInstanceUser {
user: user.user_id.into(),
permissions: user.permissions,
}
}
}