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
+16 -16
View File
@@ -6,7 +6,7 @@ use crate::database::models::loader_fields::{
use crate::database::models::thread_item::ThreadBuilder;
use crate::database::models::{self, DBUser, image_item};
use crate::database::redis::RedisPool;
use crate::file_hosting::{FileHost, FileHostingError};
use crate::file_hosting::{FileHost, FileHostPublicity, FileHostingError};
use crate::models::error::ApiError;
use crate::models::ids::{ImageId, OrganizationId, ProjectId, VersionId};
use crate::models::images::{Image, ImageContext};
@@ -240,18 +240,16 @@ pub struct NewGalleryItem {
}
pub struct UploadedFile {
pub file_id: String,
pub file_name: String,
pub name: String,
pub publicity: FileHostPublicity,
}
pub async fn undo_uploads(
file_host: &dyn FileHost,
uploaded_files: &[UploadedFile],
) -> Result<(), CreateError> {
) -> Result<(), FileHostingError> {
for file in uploaded_files {
file_host
.delete_file_version(&file.file_id, &file.file_name)
.await?;
file_host.delete_file(&file.name, file.publicity).await?;
}
Ok(())
}
@@ -309,13 +307,13 @@ Get logged in user
2. Upload
- Icon: check file format & size
- Upload to backblaze & record URL
- Upload to S3 & record URL
- Project files
- Check for matching version
- File size limits?
- Check file type
- Eventually, malware scan
- Upload to backblaze & create VersionFileBuilder
- Upload to S3 & create VersionFileBuilder
-
3. Creation
@@ -334,7 +332,7 @@ async fn project_create_inner(
redis: &RedisPool,
session_queue: &AuthQueue,
) -> Result<HttpResponse, CreateError> {
// The base URL for files uploaded to backblaze
// The base URL for files uploaded to S3
let cdn_url = dotenvy::var("CDN_URL")?;
// The currently logged in user
@@ -516,6 +514,7 @@ async fn project_create_inner(
let url = format!("data/{project_id}/images");
let upload_result = upload_image_optimized(
&url,
FileHostPublicity::Public,
data.freeze(),
file_extension,
Some(350),
@@ -526,8 +525,8 @@ async fn project_create_inner(
.map_err(|e| CreateError::InvalidIconFormat(e.to_string()))?;
uploaded_files.push(UploadedFile {
file_id: upload_result.raw_url_path.clone(),
file_name: upload_result.raw_url_path,
name: upload_result.raw_url_path,
publicity: FileHostPublicity::Public,
});
gallery_urls.push(crate::models::projects::GalleryItem {
url: upload_result.url,
@@ -1010,6 +1009,7 @@ async fn process_icon_upload(
.await?;
let upload_result = crate::util::img::upload_image_optimized(
&format!("data/{}", to_base62(id)),
FileHostPublicity::Public,
data.freeze(),
file_extension,
Some(96),
@@ -1020,13 +1020,13 @@ async fn process_icon_upload(
.map_err(|e| CreateError::InvalidIconFormat(e.to_string()))?;
uploaded_files.push(UploadedFile {
file_id: upload_result.raw_url_path.clone(),
file_name: upload_result.raw_url_path,
name: upload_result.raw_url_path,
publicity: FileHostPublicity::Public,
});
uploaded_files.push(UploadedFile {
file_id: upload_result.url_path.clone(),
file_name: upload_result.url_path,
name: upload_result.url_path,
publicity: FileHostPublicity::Public,
});
Ok((