feat: admin list

This commit is contained in:
2024-10-04 18:00:37 +01:00
parent d7b332905b
commit 2b194ad10c
19 changed files with 395 additions and 29 deletions

View File

@ -1,3 +1,7 @@
{
"recommendations": ["arcanis.vscode-zipfs", "dbaeumer.vscode-eslint"]
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

32
ui_src/.yarn/sdks/prettier/bin/prettier.cjs vendored Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire, register } = require(`module`);
const { resolve } = require(`path`);
const { pathToFileURL } = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? (exports) => absRequire(absUserWrapperPath)(exports)
: (exports) => exports;
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`));

32
ui_src/.yarn/sdks/prettier/index.cjs vendored Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env node
const { existsSync } = require(`fs`);
const { createRequire, register } = require(`module`);
const { resolve } = require(`path`);
const { pathToFileURL } = require(`url`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? (exports) => absRequire(absUserWrapperPath)(exports)
: (exports) => exports;
// Defer to the real prettier your application uses
module.exports = wrapWithUserWrapper(absRequire(`prettier`));

View File

@ -0,0 +1,7 @@
{
"name": "prettier",
"version": "3.3.3-sdk",
"main": "./index.cjs",
"type": "commonjs",
"bin": "./bin/prettier.cjs"
}

View File

@ -0,0 +1,87 @@
import { base64 } from "@scure/base";
import { throwIfOffline } from "@snort/shared";
import { EventKind, EventPublisher, NostrEvent } from "@snort/system";
export class Route96 {
constructor(
readonly url: string,
readonly publisher: EventPublisher,
) {
this.url = new URL(this.url).toString();
}
async getSelf() {
const rsp = await this.#req("/admin/self", "GET");
const data =
await this.#handleResponse<AdminResponse<{ is_admin: boolean }>>(rsp);
return data;
}
async listFiles(page = 0, count = 10) {
const rsp = await this.#req(
`/admin/files?page=${page}&count=${count}`,
"GET",
);
const data = await this.#handleResponse<AdminResponseFileList>(rsp);
return {
...data,
...data.data,
files: data.data.files,
};
}
async #handleResponse<T extends AdminResponseBase>(rsp: Response) {
if (rsp.ok) {
return (await rsp.json()) as T;
} else {
const text = await rsp.text();
try {
const obj = JSON.parse(text) as AdminResponseBase;
throw new Error(obj.message);
} catch {
throw new Error(`Upload failed: ${text}`);
}
}
}
async #req(path: string, method: "GET" | "POST" | "DELETE", body?: BodyInit) {
throwIfOffline();
const auth = async (url: string, method: string) => {
const auth = await this.publisher.generic((eb) => {
return eb
.kind(EventKind.HttpAuthentication)
.tag(["u", url])
.tag(["method", method]);
});
return `Nostr ${base64.encode(
new TextEncoder().encode(JSON.stringify(auth)),
)}`;
};
const u = `${this.url}${path}`;
return await fetch(u, {
method,
body,
headers: {
accept: "application/json",
authorization: await auth(u, method),
},
});
}
}
export interface AdminResponseBase {
status: string;
message?: string;
}
export type AdminResponse<T> = AdminResponseBase & {
data: T;
};
export type AdminResponseFileList = AdminResponse<{
total: number;
page: number;
count: number;
files: Array<NostrEvent>;
}>;

View File

@ -6,21 +6,25 @@ import { Blossom } from "../upload/blossom";
import useLogin from "../hooks/login";
import usePublisher from "../hooks/publisher";
import { Nip96, Nip96FileList } from "../upload/nip96";
import { Route96 } from "../upload/admin";
export default function Upload() {
const [type, setType] = useState<"blossom" | "nip96">("nip96");
const [noCompress, setNoCompress] = useState(false);
const [toUpload, setToUpload] = useState<File>();
const [self, setSelf] = useState<{ is_admin: boolean }>();
const [error, setError] = useState<string>();
const [results, setResults] = useState<Array<object>>([]);
const [listedFiles, setListedFiles] = useState<Nip96FileList>();
const [adminListedFiles, setAdminListedFiles] = useState<Nip96FileList>();
const [listedPage, setListedPage] = useState(0);
const [adminListedPage, setAdminListedPage] = useState(0);
const login = useLogin();
const pub = usePublisher();
const url = `${location.protocol}//${location.host}`;
//const url = "https://files.v0l.io";
//const url = "http://localhost:8000";
async function doUpload() {
if (!pub) return;
if (!toUpload) return;
@ -67,10 +71,39 @@ export default function Upload() {
}
}
async function listAllUploads(n: number) {
if (!pub) return;
try {
setError(undefined);
const uploader = new Route96(url, pub);
const result = await uploader.listFiles(n, 12);
setAdminListedFiles(result);
} catch (e) {
if (e instanceof Error) {
setError(e.message.length > 0 ? e.message : "Upload failed");
} else if (typeof e === "string") {
setError(e);
} else {
setError("List files failed");
}
}
}
useEffect(() => {
listUploads(listedPage);
}, [listedPage]);
useEffect(() => {
listAllUploads(adminListedPage);
}, [adminListedPage]);
useEffect(() => {
if (pub && !self) {
const r96 = new Route96(url, pub);
r96.getSelf().then((v) => setSelf(v.data));
}
}, [pub, self]);
return (
<div className="flex flex-col gap-2 bg-neutral-700 p-8 rounded-xl w-full">
<h1 className="text-lg font-bold">
@ -121,6 +154,7 @@ export default function Upload() {
<Button disabled={login === undefined} onClick={() => listUploads(0)}>
List Uploads
</Button>
{listedFiles && (
<FileList
files={listedFiles.files}
@ -129,6 +163,21 @@ export default function Upload() {
onPage={(x) => setListedPage(x)}
/>
)}
{self?.is_admin && (
<>
<h3>Admin File List:</h3>
<Button onClick={() => listAllUploads(0)}>List All Uploads</Button>
{adminListedFiles && (
<FileList
files={adminListedFiles.files}
pages={adminListedFiles.total / adminListedFiles.count}
page={adminListedFiles.page}
onPage={(x) => setAdminListedPage(x)}
/>
)}
</>
)}
{error && <b className="text-red-500">{error}</b>}
<pre className="text-xs font-monospace overflow-wrap">
{JSON.stringify(results, undefined, 2)}