mirror of
https://github.com/v0l/route96.git
synced 2025-06-19 07:04:58 +00:00
fix: AI slop
This commit is contained in:
@ -5,7 +5,7 @@ use crate::routes::{delete_file, Nip94Event};
|
|||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use log::error;
|
use log::error;
|
||||||
use nostr::prelude::hex;
|
use nostr::prelude::hex;
|
||||||
use nostr::{Alphabet, SingleLetterTag, TagKind};
|
use nostr::{Alphabet, JsonUtil, SingleLetterTag, TagKind};
|
||||||
use rocket::data::ByteUnit;
|
use rocket::data::ByteUnit;
|
||||||
use rocket::futures::StreamExt;
|
use rocket::futures::StreamExt;
|
||||||
use rocket::http::{Header, Status};
|
use rocket::http::{Header, Status};
|
||||||
@ -15,6 +15,7 @@ use rocket::{routes, Data, Request, Response, Route, State};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::AsyncRead;
|
use tokio::io::AsyncRead;
|
||||||
use tokio_util::io::StreamReader;
|
use tokio_util::io::StreamReader;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
@ -225,15 +226,25 @@ async fn mirror(
|
|||||||
settings: &State<Settings>,
|
settings: &State<Settings>,
|
||||||
req: Json<MirrorRequest>,
|
req: Json<MirrorRequest>,
|
||||||
) -> BlossomResponse {
|
) -> BlossomResponse {
|
||||||
if !check_method(&auth.event, "mirror") {
|
if !check_method(&auth.event, "upload") {
|
||||||
return BlossomResponse::error("Invalid request method tag");
|
return BlossomResponse::error("Invalid request method tag");
|
||||||
}
|
}
|
||||||
if let Some(e) = check_whitelist(&auth, settings) {
|
if let Some(e) = check_whitelist(&auth, settings) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let url = match Url::parse(&req.url) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => return BlossomResponse::error(format!("Invalid URL: {}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let hash = url
|
||||||
|
.path_segments()
|
||||||
|
.and_then(|mut c| c.next_back())
|
||||||
|
.and_then(|s| s.split(".").next());
|
||||||
|
|
||||||
// download file
|
// download file
|
||||||
let rsp = match reqwest::get(&req.url).await {
|
let rsp = match reqwest::get(url.clone()).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error downloading file: {}", e);
|
error!("Error downloading file: {}", e);
|
||||||
return BlossomResponse::error("Failed to mirror file");
|
return BlossomResponse::error("Failed to mirror file");
|
||||||
@ -250,9 +261,10 @@ async fn mirror(
|
|||||||
let pubkey = auth.event.pubkey.to_bytes().to_vec();
|
let pubkey = auth.event.pubkey.to_bytes().to_vec();
|
||||||
|
|
||||||
process_stream(
|
process_stream(
|
||||||
StreamReader::new(rsp.bytes_stream().map(|result| {
|
StreamReader::new(
|
||||||
result.map_err(std::io::Error::other)
|
rsp.bytes_stream()
|
||||||
})),
|
.map(|result| result.map_err(std::io::Error::other)),
|
||||||
|
),
|
||||||
&mime_type,
|
&mime_type,
|
||||||
&None,
|
&None,
|
||||||
&pubkey,
|
&pubkey,
|
||||||
@ -261,6 +273,7 @@ async fn mirror(
|
|||||||
fs,
|
fs,
|
||||||
db,
|
db,
|
||||||
settings,
|
settings,
|
||||||
|
hash.and_then(|h| hex::decode(h).ok()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -367,15 +380,11 @@ async fn process_upload(
|
|||||||
// check quota (only if payments are configured)
|
// check quota (only if payments are configured)
|
||||||
#[cfg(feature = "payments")]
|
#[cfg(feature = "payments")]
|
||||||
if let Some(payment_config) = &settings.payments {
|
if let Some(payment_config) = &settings.payments {
|
||||||
let free_quota = payment_config.free_quota_bytes
|
let free_quota = payment_config.free_quota_bytes.unwrap_or(104857600); // Default to 100MB
|
||||||
.unwrap_or(104857600); // Default to 100MB
|
|
||||||
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
let pubkey_vec = auth.event.pubkey.to_bytes().to_vec();
|
||||||
|
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
match db
|
match db.check_user_quota(&pubkey_vec, size, free_quota).await {
|
||||||
.check_user_quota(&pubkey_vec, size, free_quota)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(false) => return BlossomResponse::error("Upload would exceed quota"),
|
Ok(false) => return BlossomResponse::error("Upload would exceed quota"),
|
||||||
Err(_) => return BlossomResponse::error("Failed to check quota"),
|
Err(_) => return BlossomResponse::error("Failed to check quota"),
|
||||||
Ok(true) => {} // Quota check passed
|
Ok(true) => {} // Quota check passed
|
||||||
@ -395,6 +404,7 @@ async fn process_upload(
|
|||||||
fs,
|
fs,
|
||||||
db,
|
db,
|
||||||
settings,
|
settings,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -409,6 +419,7 @@ async fn process_stream<'p, S>(
|
|||||||
fs: &State<FileStore>,
|
fs: &State<FileStore>,
|
||||||
db: &State<Database>,
|
db: &State<Database>,
|
||||||
settings: &State<Settings>,
|
settings: &State<Settings>,
|
||||||
|
expect_hash: Option<Vec<u8>>,
|
||||||
) -> BlossomResponse
|
) -> BlossomResponse
|
||||||
where
|
where
|
||||||
S: AsyncRead + Unpin + 'p,
|
S: AsyncRead + Unpin + 'p,
|
||||||
@ -417,6 +428,15 @@ where
|
|||||||
Ok(FileSystemResult::NewFile(blob)) => {
|
Ok(FileSystemResult::NewFile(blob)) => {
|
||||||
let mut ret: FileUpload = (&blob).into();
|
let mut ret: FileUpload = (&blob).into();
|
||||||
|
|
||||||
|
// check expected hash (mirroring)
|
||||||
|
if let Some(h) = expect_hash {
|
||||||
|
if h != ret.id {
|
||||||
|
if let Err(e) = tokio::fs::remove_file(fs.get(&ret.id)).await {
|
||||||
|
log::warn!("Failed to cleanup file: {}", e);
|
||||||
|
}
|
||||||
|
return BlossomResponse::error("Mirror request failed, server responses with invalid file content (hash mismatch)");
|
||||||
|
}
|
||||||
|
}
|
||||||
// update file data before inserting
|
// update file data before inserting
|
||||||
ret.name = name.map(|s| s.to_string());
|
ret.name = name.map(|s| s.to_string());
|
||||||
|
|
||||||
@ -443,8 +463,7 @@ where
|
|||||||
#[cfg(feature = "payments")]
|
#[cfg(feature = "payments")]
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
if let Some(payment_config) = &settings.payments {
|
if let Some(payment_config) = &settings.payments {
|
||||||
let free_quota = payment_config.free_quota_bytes
|
let free_quota = payment_config.free_quota_bytes.unwrap_or(104857600); // Default to 100MB
|
||||||
.unwrap_or(104857600); // Default to 100MB
|
|
||||||
|
|
||||||
match db.check_user_quota(pubkey, upload.size, free_quota).await {
|
match db.check_user_quota(pubkey, upload.size, free_quota).await {
|
||||||
Ok(false) => {
|
Ok(false) => {
|
||||||
|
@ -35,6 +35,7 @@ export default function MirrorSuggestions({ servers }: MirrorSuggestionsProps) {
|
|||||||
|
|
||||||
async function fetchSuggestions() {
|
async function fetchSuggestions() {
|
||||||
if (!pub || !login?.pubkey) return;
|
if (!pub || !login?.pubkey) return;
|
||||||
|
if (loading) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -181,7 +182,7 @@ export default function MirrorSuggestions({ servers }: MirrorSuggestionsProps) {
|
|||||||
<div className="flex items-start justify-between mb-3">
|
<div className="flex items-start justify-between mb-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm font-medium text-gray-300 mb-1">
|
<p className="text-sm font-medium text-gray-300 mb-1">
|
||||||
File: {suggestion.sha256.substring(0, 16)}...
|
File: {suggestion.sha256}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-400">
|
<p className="text-xs text-gray-400">
|
||||||
Size: {FormatBytes(suggestion.size)}
|
Size: {FormatBytes(suggestion.size)}
|
||||||
|
@ -43,3 +43,6 @@ export function FormatBytes(b: number, f?: number) {
|
|||||||
if (b >= kiB) return (b / kiB).toFixed(f) + " KiB";
|
if (b >= kiB) return (b / kiB).toFixed(f) + " KiB";
|
||||||
return b.toFixed(f) + " B";
|
return b.toFixed(f) + " B";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ServerUrl =
|
||||||
|
import.meta.env.VITE_API_URL || `${location.protocol}//${location.host}`;
|
@ -1,26 +1,22 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import useLogin from "./login";
|
import useLogin from "./login";
|
||||||
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
import { EventKind, RequestBuilder } from "@snort/system";
|
||||||
|
import { appendDedupe, dedupe, removeUndefined, sanitizeRelayUrl } from "@snort/shared";
|
||||||
|
import { ServerUrl } from "../const";
|
||||||
|
|
||||||
const DefaultMediaServers = ["https://cdn.satellite.earth/", "https://cdn.self.hosted/"];
|
const DefaultMediaServers = ["https://blossom.band/", "https://blossom.primal.net", ServerUrl];
|
||||||
|
|
||||||
export function useBlossomServers() {
|
export function useBlossomServers() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
|
|
||||||
return useMemo(() => {
|
const rb = new RequestBuilder("media-servers");
|
||||||
// For now, just return default servers
|
if (login?.pubkey) {
|
||||||
// TODO: Implement proper nostr event kind 10063 querying when system supports it
|
rb.withFilter()
|
||||||
const servers = DefaultMediaServers;
|
.kinds([10_063 as EventKind])
|
||||||
|
.authors([login.pubkey]);
|
||||||
return {
|
}
|
||||||
servers,
|
const req = useRequestBuilder(rb);
|
||||||
addServer: async (serverUrl: string) => {
|
|
||||||
// TODO: Implement adding server to event kind 10063
|
const servers = req === undefined ? undefined : dedupe(removeUndefined(req.flatMap((e) => e.tags.filter(t => t[0] === "server").map((t) => sanitizeRelayUrl(t[1])))));
|
||||||
console.log("Adding server not implemented yet:", serverUrl);
|
return appendDedupe(DefaultMediaServers, servers);
|
||||||
},
|
|
||||||
removeServer: async (serverUrl: string) => {
|
|
||||||
// TODO: Implement removing server from event kind 10063
|
|
||||||
console.log("Removing server not implemented yet:", serverUrl);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, [login?.pubkey]);
|
|
||||||
}
|
}
|
@ -65,7 +65,7 @@ export class Blossom {
|
|||||||
const rsp = await this.#req(
|
const rsp = await this.#req(
|
||||||
"mirror",
|
"mirror",
|
||||||
"PUT",
|
"PUT",
|
||||||
"mirror",
|
"upload",
|
||||||
JSON.stringify({ url }),
|
JSON.stringify({ url }),
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@ import useLogin from "../hooks/login";
|
|||||||
import usePublisher from "../hooks/publisher";
|
import usePublisher from "../hooks/publisher";
|
||||||
import { Nip96, Nip96FileList } from "../upload/nip96";
|
import { Nip96, Nip96FileList } from "../upload/nip96";
|
||||||
import { AdminSelf, Route96 } from "../upload/admin";
|
import { AdminSelf, Route96 } from "../upload/admin";
|
||||||
import { FormatBytes } from "../const";
|
import { FormatBytes, ServerUrl } from "../const";
|
||||||
import { UploadProgress } from "../upload/progress";
|
import { UploadProgress } from "../upload/progress";
|
||||||
|
|
||||||
export default function Upload() {
|
export default function Upload() {
|
||||||
@ -25,14 +25,11 @@ export default function Upload() {
|
|||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const [uploadProgress, setUploadProgress] = useState<UploadProgress>();
|
const [uploadProgress, setUploadProgress] = useState<UploadProgress>();
|
||||||
|
|
||||||
const { servers: blossomServers } = useBlossomServers();
|
const blossomServers = useBlossomServers();
|
||||||
|
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const pub = usePublisher();
|
const pub = usePublisher();
|
||||||
|
|
||||||
const url =
|
|
||||||
import.meta.env.VITE_API_URL || `${location.protocol}//${location.host}`;
|
|
||||||
|
|
||||||
// Check if file should have compression enabled by default
|
// Check if file should have compression enabled by default
|
||||||
const shouldCompress = (file: File) => {
|
const shouldCompress = (file: File) => {
|
||||||
return file.type.startsWith('video/') || file.type.startsWith('image/');
|
return file.type.startsWith('video/') || file.type.startsWith('image/');
|
||||||
@ -52,7 +49,7 @@ export default function Upload() {
|
|||||||
setUploadProgress(progress);
|
setUploadProgress(progress);
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploader = new Blossom(url, pub);
|
const uploader = new Blossom(ServerUrl, pub);
|
||||||
// Use compression by default for video and image files, unless explicitly disabled
|
// Use compression by default for video and image files, unless explicitly disabled
|
||||||
const useCompression = shouldCompress(file) && !noCompress;
|
const useCompression = shouldCompress(file) && !noCompress;
|
||||||
const result = useCompression
|
const result = useCompression
|
||||||
@ -99,7 +96,7 @@ export default function Upload() {
|
|||||||
if (!pub) return;
|
if (!pub) return;
|
||||||
try {
|
try {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
const uploader = new Nip96(url, pub);
|
const uploader = new Nip96(ServerUrl, pub);
|
||||||
await uploader.loadInfo();
|
await uploader.loadInfo();
|
||||||
const result = await uploader.listFiles(n, 50);
|
const result = await uploader.listFiles(n, 50);
|
||||||
setListedFiles(result);
|
setListedFiles(result);
|
||||||
@ -115,14 +112,14 @@ export default function Upload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pub, url],
|
[pub],
|
||||||
);
|
);
|
||||||
|
|
||||||
async function deleteFile(id: string) {
|
async function deleteFile(id: string) {
|
||||||
if (!pub) return;
|
if (!pub) return;
|
||||||
try {
|
try {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
const uploader = new Blossom(url, pub);
|
const uploader = new Blossom(ServerUrl, pub);
|
||||||
await uploader.delete(id);
|
await uploader.delete(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
@ -143,10 +140,10 @@ export default function Upload() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pub && !self) {
|
if (pub && !self) {
|
||||||
const r96 = new Route96(url, pub);
|
const r96 = new Route96(ServerUrl, pub);
|
||||||
r96.getSelf().then((v) => setSelf(v.data));
|
r96.getSelf().then((v) => setSelf(v.data));
|
||||||
}
|
}
|
||||||
}, [pub, self, url]);
|
}, [pub, self]);
|
||||||
|
|
||||||
if (!login) {
|
if (!login) {
|
||||||
return (
|
return (
|
||||||
@ -240,8 +237,7 @@ export default function Upload() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-700 rounded-full h-2.5">
|
<div className="w-full bg-gray-700 rounded-full h-2.5">
|
||||||
<div
|
<div
|
||||||
className={`h-2.5 rounded-full transition-all duration-300 ${
|
className={`h-2.5 rounded-full transition-all duration-300 ${self.total_size / self.total_available_quota > 0.8
|
||||||
self.total_size / self.total_available_quota > 0.8
|
|
||||||
? "bg-red-500"
|
? "bg-red-500"
|
||||||
: self.total_size / self.total_available_quota > 0.6
|
: self.total_size / self.total_available_quota > 0.6
|
||||||
? "bg-yellow-500"
|
? "bg-yellow-500"
|
||||||
@ -261,8 +257,7 @@ export default function Upload() {
|
|||||||
% used
|
% used
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`${
|
className={`${self.total_size / self.total_available_quota > 0.8
|
||||||
self.total_size / self.total_available_quota > 0.8
|
|
||||||
? "text-red-400"
|
? "text-red-400"
|
||||||
: self.total_size / self.total_available_quota > 0.6
|
: self.total_size / self.total_available_quota > 0.6
|
||||||
? "text-yellow-400"
|
? "text-yellow-400"
|
||||||
@ -332,7 +327,7 @@ export default function Upload() {
|
|||||||
{showPaymentFlow && pub && (
|
{showPaymentFlow && pub && (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<PaymentFlow
|
<PaymentFlow
|
||||||
route96={new Route96(url, pub)}
|
route96={new Route96(ServerUrl, pub)}
|
||||||
onPaymentRequested={(pr) => {
|
onPaymentRequested={(pr) => {
|
||||||
console.log("Payment requested:", pr);
|
console.log("Payment requested:", pr);
|
||||||
}}
|
}}
|
||||||
@ -342,7 +337,7 @@ export default function Upload() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mirror Suggestions */}
|
{/* Mirror Suggestions */}
|
||||||
{blossomServers.length > 1 && (
|
{blossomServers && blossomServers.length > 1 && (
|
||||||
<MirrorSuggestions
|
<MirrorSuggestions
|
||||||
servers={blossomServers}
|
servers={blossomServers}
|
||||||
/>
|
/>
|
||||||
|
Reference in New Issue
Block a user