CI / check (push) Successful in 30s
CI / tests-imaging-browsers (push) Successful in 1m31s
CI / tests-unit (push) Successful in 1m45s
CI / tests-integration (push) Successful in 1m49s
CI / tests-quality (push) Successful in 47s
CI / tests-ui (push) Successful in 4m24s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m39s
The project lints with Biome only, so none of these 78 directives across 57 files ever suppressed anything. Directives that carried the rationale for a flagged dynamic index access keep that rationale as a plain comment.
33 lines
1002 B
TypeScript
33 lines
1002 B
TypeScript
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { apiOk } from "@/lib/api";
|
|
import { withAdmin } from "@/lib/api-handler";
|
|
import { MEDIA_ROOT } from "@/lib/media-storage";
|
|
import { PERMS } from "@/lib/permissions";
|
|
|
|
/**
|
|
* Directory listing for the admin media picker. It used to be world-readable and
|
|
* unrated, which handed every visitor a complete inventory of uploaded media
|
|
* (logo/favicon paths included) plus their timestamps.
|
|
*/
|
|
export const GET = withAdmin({ permission: PERMS.PAGES_EDIT }, async () => {
|
|
const dir = MEDIA_ROOT;
|
|
if (!existsSync(dir)) {
|
|
return apiOk({ files: [] });
|
|
}
|
|
const files = readdirSync(dir)
|
|
.filter((f) => /\.(png|jpg|jpeg|gif|webp|svg|bmp)$/i.test(f))
|
|
.map((f) => {
|
|
const stat = statSync(resolve(dir, f));
|
|
return {
|
|
name: f,
|
|
url: `/api/media/${f}`,
|
|
size: stat.size,
|
|
uploaded: stat.mtimeMs,
|
|
};
|
|
})
|
|
.sort((a, b) => b.uploaded - a.uploaded);
|
|
|
|
return apiOk({ files });
|
|
});
|