Compare commits
No commits in common. "c12b9b360a086d5459db35f9c17932060dc0efcc" and "6726cafff4839871be1e489a047127c1d1c3284e" have entirely different histories.
c12b9b360a
...
6726cafff4
@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env zx
|
|
||||||
import { glob } from 'node:fs/promises';
|
|
||||||
import os from 'node:os';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { chunk } from 'es-toolkit/array';
|
|
||||||
|
|
||||||
const dataDir = path.join(import.meta.dirname, '../../../data')
|
|
||||||
/**
|
|
||||||
* @type {string[]}
|
|
||||||
*/
|
|
||||||
const images = [];
|
|
||||||
for await (const image of glob('**/*.{jpg,jpeg,png,gif,svg}', {
|
|
||||||
cwd: dataDir,
|
|
||||||
})) {
|
|
||||||
images.push(image)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cpus = os.cpus().length - 1;
|
|
||||||
|
|
||||||
const chunkSize = Math.ceil(images.length / cpus);
|
|
||||||
const chunks = chunk(images, chunkSize);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string[]} images
|
|
||||||
*/
|
|
||||||
async function convertImages(images) {
|
|
||||||
for await (const image of images) {
|
|
||||||
const imagePath = path.resolve(dataDir, image)
|
|
||||||
const webp = imagePath.replace(path.extname(imagePath), '.webp')
|
|
||||||
const avif = imagePath.replace(path.extname(imagePath), '.avif')
|
|
||||||
console.log(`Converting ${imagePath} to ${webp}...`);
|
|
||||||
await $`ffmpeg -i "${imagePath}" -c:v libwebp -lossless 1 "${webp}"`;
|
|
||||||
console.log(`Converting ${imagePath} to ${avif}...`);
|
|
||||||
await $`ffmpeg -i "${imagePath}" -c:v libaom-av1 -still-picture 1 -pix_fmt yuv420p10le -crf 0 -strict experimental "${avif}"`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
chunks.map(convertImages)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,18 +26,18 @@ host = '{{ get_env(name="HOST", default="localhost") }}'
|
|||||||
enable = true
|
enable = true
|
||||||
|
|
||||||
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||||
[server.middlewares.request_id]
|
[server.middleware.request_id]
|
||||||
enable = true
|
enable = true
|
||||||
|
|
||||||
[server.middlewares.logger]
|
[server.middleware.logger]
|
||||||
enable = true
|
enable = true
|
||||||
|
|
||||||
# when your code is panicked, the request still returns 500 status code.
|
# when your code is panicked, the request still returns 500 status code.
|
||||||
[server.middlewares.catch_panic]
|
[server.middleware.catch_panic]
|
||||||
enable = true
|
enable = true
|
||||||
|
|
||||||
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||||
[server.middlewares.timeout_request]
|
[server.middleware.timeout_request]
|
||||||
enable = false
|
enable = false
|
||||||
# Duration time in milliseconds.
|
# Duration time in milliseconds.
|
||||||
timeout = 5000
|
timeout = 5000
|
||||||
@ -53,10 +53,7 @@ timeout = 5000
|
|||||||
# - POST
|
# - POST
|
||||||
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
|
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
|
||||||
# max_age: 3600
|
# max_age: 3600
|
||||||
[server.middlewares.cors]
|
[server.middleware.cors]
|
||||||
enable = true
|
|
||||||
|
|
||||||
[server.middlewares.compression]
|
|
||||||
enable = true
|
enable = true
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
|
@ -6,7 +6,6 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use super::{builder::AppBuilder, context::AppContextTrait};
|
use super::{builder::AppBuilder, context::AppContextTrait};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::Environment,
|
|
||||||
errors::{RecorderError, RecorderResult},
|
errors::{RecorderError, RecorderResult},
|
||||||
web::{
|
web::{
|
||||||
controller::{self, core::ControllerTrait},
|
controller::{self, core::ControllerTrait},
|
||||||
@ -65,10 +64,8 @@ impl App {
|
|||||||
|
|
||||||
let middlewares = default_middleware_stack(context.clone());
|
let middlewares = default_middleware_stack(context.clone());
|
||||||
for mid in middlewares {
|
for mid in middlewares {
|
||||||
if mid.is_enabled() {
|
router = mid.apply(router)?;
|
||||||
router = mid.apply(router)?;
|
tracing::info!(name = mid.name(), "+middleware");
|
||||||
tracing::info!(name = mid.name(), "+middleware");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let router = router
|
let router = router
|
||||||
@ -89,17 +86,13 @@ impl App {
|
|||||||
async {
|
async {
|
||||||
{
|
{
|
||||||
let monitor = task.setup_monitor().await?;
|
let monitor = task.setup_monitor().await?;
|
||||||
if matches!(context.environment(), Environment::Development) {
|
monitor
|
||||||
monitor.run().await?;
|
.run_with_signal(async move {
|
||||||
} else {
|
Self::shutdown_signal().await;
|
||||||
monitor
|
tracing::info!("apalis shutting down...");
|
||||||
.run_with_signal(async move {
|
Ok(())
|
||||||
Self::shutdown_signal().await;
|
})
|
||||||
tracing::info!("apalis shutting down...");
|
.await?;
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), RecorderError>(())
|
Ok::<(), RecorderError>(())
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{borrow::Cow, fmt};
|
use std::fmt;
|
||||||
|
|
||||||
use async_stream::try_stream;
|
use async_stream::try_stream;
|
||||||
use axum::{body::Body, response::Response};
|
use axum::{body::Body, response::Response};
|
||||||
@ -206,12 +206,6 @@ impl StorageService {
|
|||||||
let mime_type = mime_guess::from_path(storage_path.as_ref()).first_or_octet_stream();
|
let mime_type = mime_guess::from_path(storage_path.as_ref()).first_or_octet_stream();
|
||||||
|
|
||||||
let content_type = HeaderValue::from_str(mime_type.as_ref())?;
|
let content_type = HeaderValue::from_str(mime_type.as_ref())?;
|
||||||
let etag = metadata.etag().map(Cow::Borrowed).or_else(|| {
|
|
||||||
let len = metadata.content_length();
|
|
||||||
let lm = metadata.last_modified()?.timestamp();
|
|
||||||
Some(Cow::Owned(format!("\"{lm:x}-{len:x}\"")))
|
|
||||||
});
|
|
||||||
let last_modified = metadata.last_modified().map(|lm| lm.to_rfc2822());
|
|
||||||
|
|
||||||
let response = if let Some(TypedHeader(range)) = range {
|
let response = if let Some(TypedHeader(range)) = range {
|
||||||
let ranges = range
|
let ranges = range
|
||||||
@ -246,7 +240,7 @@ impl StorageService {
|
|||||||
};
|
};
|
||||||
let body = Body::from_stream(stream);
|
let body = Body::from_stream(stream);
|
||||||
|
|
||||||
let mut builder = Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::PARTIAL_CONTENT)
|
.status(StatusCode::PARTIAL_CONTENT)
|
||||||
.header(
|
.header(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
@ -254,34 +248,17 @@ impl StorageService {
|
|||||||
format!("multipart/byteranges; boundary={boundary}").as_str(),
|
format!("multipart/byteranges; boundary={boundary}").as_str(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
)
|
||||||
|
.body(body)?
|
||||||
if let Some(etag) = etag {
|
|
||||||
builder = builder.header(header::ETAG, etag.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(last_modified) = last_modified {
|
|
||||||
builder = builder.header(header::LAST_MODIFIED, last_modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.body(body)?
|
|
||||||
} else if let Some((r, content_range)) = ranges.pop() {
|
} else if let Some((r, content_range)) = ranges.pop() {
|
||||||
let reader = self.reader(storage_path.as_ref()).await?;
|
let reader = self.reader(storage_path.as_ref()).await?;
|
||||||
let stream = reader.into_bytes_stream(r).await?;
|
let stream = reader.into_bytes_stream(r).await?;
|
||||||
|
|
||||||
let mut builder = Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::PARTIAL_CONTENT)
|
.status(StatusCode::PARTIAL_CONTENT)
|
||||||
.header(header::CONTENT_TYPE, content_type.clone())
|
.header(header::CONTENT_TYPE, content_type.clone())
|
||||||
.header(header::CONTENT_RANGE, content_range);
|
.header(header::CONTENT_RANGE, content_range)
|
||||||
|
.body(Body::from_stream(stream))?
|
||||||
if let Some(etag) = metadata.etag() {
|
|
||||||
builder = builder.header(header::ETAG, etag);
|
|
||||||
}
|
|
||||||
if let Some(last_modified) = last_modified {
|
|
||||||
builder = builder.header(header::LAST_MODIFIED, last_modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.body(Body::from_stream(stream))?
|
|
||||||
} else {
|
} else {
|
||||||
unreachable!("ranges length should be greater than 0")
|
unreachable!("ranges length should be greater than 0")
|
||||||
}
|
}
|
||||||
@ -299,19 +276,10 @@ impl StorageService {
|
|||||||
let reader = self.reader(storage_path.as_ref()).await?;
|
let reader = self.reader(storage_path.as_ref()).await?;
|
||||||
let stream = reader.into_bytes_stream(..).await?;
|
let stream = reader.into_bytes_stream(..).await?;
|
||||||
|
|
||||||
let mut builder = Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
.header(header::CONTENT_TYPE, content_type);
|
.header(header::CONTENT_TYPE, content_type)
|
||||||
|
.body(Body::from_stream(stream))?
|
||||||
if let Some(etag) = etag {
|
|
||||||
builder = builder.header(header::ETAG, etag.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(last_modified) = last_modified {
|
|
||||||
builder = builder.header(header::LAST_MODIFIED, last_modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.body(Body::from_stream(stream))?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: apps/recorder/src/web/middleware/request_id.rs
|
||||||
|
assertion_line: 126
|
||||||
|
expression: id
|
||||||
|
---
|
||||||
|
"foo-barbaz"
|
@ -77,6 +77,7 @@
|
|||||||
"tw-animate-css": "^1.3.4",
|
"tw-animate-css": "^1.3.4",
|
||||||
"type-fest": "^4.41.0",
|
"type-fest": "^4.41.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
|
"es-toolkit": "^1.39.3",
|
||||||
"@tanstack/react-router": "^1.121.2"
|
"@tanstack/react-router": "^1.121.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,38 +1,9 @@
|
|||||||
import { type ComponentProps, useMemo, useState } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
export type ImgProps = Omit<ComponentProps<"img">, "alt"> &
|
export type ImgProps = Omit<ComponentProps<"img">, "alt"> &
|
||||||
Required<Pick<ComponentProps<"img">, "alt">> & {
|
Required<Pick<ComponentProps<"img">, "alt">>;
|
||||||
optimize?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LEGACY_IMAGE_REGEX = /\.(jpg|jpeg|png|gif|svg)$/;
|
|
||||||
|
|
||||||
export const Img = (props: ImgProps) => {
|
export const Img = (props: ImgProps) => {
|
||||||
const src = props.src;
|
// biome-ignore lint/nursery/noImgElement: <explanation>
|
||||||
|
return <img {...props} alt={props.alt} />;
|
||||||
const isLegacy = useMemo(() => src?.match(LEGACY_IMAGE_REGEX), [src]);
|
|
||||||
const [isError, setIsError] = useState(false);
|
|
||||||
|
|
||||||
if (!src) {
|
|
||||||
// biome-ignore lint/nursery/noImgElement: <explanation>
|
|
||||||
return <img {...props} alt={props.alt} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<picture {...props}>
|
|
||||||
{isLegacy && !isError && (
|
|
||||||
<>
|
|
||||||
<source
|
|
||||||
srcSet={src.replace(LEGACY_IMAGE_REGEX, ".webp")}
|
|
||||||
type="image/webp"
|
|
||||||
/>
|
|
||||||
<source
|
|
||||||
srcSet={src.replace(LEGACY_IMAGE_REGEX, ".avif")}
|
|
||||||
type="image/avif"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<img {...props} alt={props.alt} onError={() => setIsError(true)} />
|
|
||||||
</picture>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
3
justfile
3
justfile
@ -11,9 +11,6 @@ prepare-dev-testcontainers:
|
|||||||
docker pull ghcr.io/dumtruck/konobangu-testing-torrents:latest
|
docker pull ghcr.io/dumtruck/konobangu-testing-torrents:latest
|
||||||
docker pull postgres:17-alpine
|
docker pull postgres:17-alpine
|
||||||
|
|
||||||
dev-optimize-images:
|
|
||||||
npx -y zx apps/recorder/examples/optimize_image.mjs
|
|
||||||
|
|
||||||
dev-webui:
|
dev-webui:
|
||||||
pnpm run --filter=webui dev
|
pnpm run --filter=webui dev
|
||||||
|
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"es-toolkit": "^1.39.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@types/node": "^24.0.1",
|
"@types/node": "^24.0.1",
|
||||||
|
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@ -10,10 +10,6 @@ overrides:
|
|||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
|
||||||
es-toolkit:
|
|
||||||
specifier: ^1.39.3
|
|
||||||
version: 1.39.3
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 1.9.4
|
specifier: 1.9.4
|
||||||
@ -213,6 +209,9 @@ importers:
|
|||||||
embla-carousel-react:
|
embla-carousel-react:
|
||||||
specifier: ^8.6.0
|
specifier: ^8.6.0
|
||||||
version: 8.6.0(react@19.1.0)
|
version: 8.6.0(react@19.1.0)
|
||||||
|
es-toolkit:
|
||||||
|
specifier: ^1.39.3
|
||||||
|
version: 1.39.3
|
||||||
graphiql:
|
graphiql:
|
||||||
specifier: ^4.1.2
|
specifier: ^4.1.2
|
||||||
version: 4.1.2(@codemirror/language@6.11.1)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
|
version: 4.1.2(@codemirror/language@6.11.1)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
|
||||||
|
Loading…
Reference in New Issue
Block a user