chore: 更新学生档案数据
This commit is contained in:
329
node_modules/modern-tar/dist/web/index.d.ts
generated
vendored
Normal file
329
node_modules/modern-tar/dist/web/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
import { i as UnpackOptions, n as TarEntryData, r as TarHeader, t as DecoderOptions } from "../types-D19dF2SE.js";
|
||||
|
||||
//#region src/web/compression.d.ts
|
||||
/**
|
||||
* Creates a gzip compression stream that is compatible with Uint8Array streams.
|
||||
*
|
||||
* @returns A {@link ReadableWritablePair} configured for gzip compression.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { createGzipEncoder, createTarPacker } from 'modern-tar';
|
||||
*
|
||||
* // Create and compress a tar archive
|
||||
* const { readable, controller } = createTarPacker();
|
||||
* const compressedStream = readable.pipeThrough(createGzipEncoder());
|
||||
*
|
||||
* // Add entries...
|
||||
* const fileStream = controller.add({ name: "file.txt", size: 5, type: "file" });
|
||||
* const writer = fileStream.getWriter();
|
||||
* await writer.write(new TextEncoder().encode("hello"));
|
||||
* await writer.close();
|
||||
* controller.finalize();
|
||||
*
|
||||
* // Upload compressed .tar.gz
|
||||
* await fetch('/api/upload', {
|
||||
* method: 'POST',
|
||||
* body: compressedStream,
|
||||
* headers: { 'Content-Type': 'application/gzip' }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
declare function createGzipEncoder(): ReadableWritablePair<Uint8Array, Uint8Array>;
|
||||
/**
|
||||
* Creates a gzip decompression stream that is compatible with Uint8Array streams.
|
||||
*
|
||||
* @returns A {@link ReadableWritablePair} configured for gzip decompression.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { createGzipDecoder, createTarDecoder } from 'modern-tar';
|
||||
*
|
||||
* // Download and process a .tar.gz file
|
||||
* const response = await fetch('https://api.example.com/archive.tar.gz');
|
||||
* if (!response.body) throw new Error('No response body');
|
||||
*
|
||||
* // Buffer entire archive
|
||||
* const entries = await unpackTar(response.body.pipeThrough(createGzipDecoder()));
|
||||
*
|
||||
* for (const entry of entries) {
|
||||
* console.log(`Extracted: ${entry.header.name}`);
|
||||
* const content = new TextDecoder().decode(entry.data);
|
||||
* console.log(`Content: ${content}`);
|
||||
* }
|
||||
* ```
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { createGzipDecoder, createTarDecoder } from 'modern-tar';
|
||||
*
|
||||
* // Download and process a .tar.gz file
|
||||
* const response = await fetch('https://api.example.com/archive.tar.gz');
|
||||
* if (!response.body) throw new Error('No response body');
|
||||
*
|
||||
* // Chain decompression and tar parsing using streams
|
||||
* const entries = response.body
|
||||
* .pipeThrough(createGzipDecoder())
|
||||
* .pipeThrough(createTarDecoder());
|
||||
*
|
||||
* for await (const entry of entries) {
|
||||
* console.log(`Extracted: ${entry.header.name}`);
|
||||
* // Process entry.body ReadableStream as needed
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
declare function createGzipDecoder(): ReadableWritablePair<Uint8Array, Uint8Array>;
|
||||
//#endregion
|
||||
//#region src/web/types.d.ts
|
||||
/**
|
||||
* Represents a complete entry to be packed into a tar archive.
|
||||
*
|
||||
* Combines header metadata with optional body data. Used as input to {@link packTar}
|
||||
* and the controller returned by {@link createTarPacker}.
|
||||
*/
|
||||
interface TarEntry {
|
||||
header: TarHeader;
|
||||
body?: TarEntryData | ReadableStream<Uint8Array>;
|
||||
}
|
||||
/**
|
||||
* Represents an entry parsed from a tar archive stream.
|
||||
*/
|
||||
interface ParsedTarEntry {
|
||||
header: TarHeader;
|
||||
body: ReadableStream<Uint8Array>;
|
||||
}
|
||||
/**
|
||||
* Represents an extracted entry with fully buffered content.
|
||||
*
|
||||
* For bodyless entries (directories, symlinks, hardlinks), `data` will be `undefined`.
|
||||
* For files (including empty files), `data` will be a `Uint8Array`.
|
||||
*/
|
||||
interface ParsedTarEntryWithData {
|
||||
header: TarHeader;
|
||||
data?: Uint8Array;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/web/helpers.d.ts
|
||||
/**
|
||||
* Packs an array of tar entries into a single `Uint8Array` buffer.
|
||||
*
|
||||
* For streaming scenarios or large archives, use {@link createTarPacker} instead.
|
||||
*
|
||||
* @param entries - Array of tar entries with headers and optional bodies
|
||||
* @returns A `Promise` that resolves to the complete tar archive as a Uint8Array
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { packTar } from 'modern-tar';
|
||||
*
|
||||
* const entries = [
|
||||
* {
|
||||
* header: { name: "hello.txt", size: 5, type: "file" },
|
||||
* body: "hello"
|
||||
* },
|
||||
* {
|
||||
* header: { name: "data.json", size: 13, type: "file" },
|
||||
* body: new Uint8Array([123, 34, 116, 101, 115, 116, 34, 58, 116, 114, 117, 101, 125]) // {"test":true}
|
||||
* },
|
||||
* {
|
||||
* header: { name: "folder/", type: "directory", size: 0 }
|
||||
* }
|
||||
* ];
|
||||
*
|
||||
* const tarBuffer = await packTar(entries);
|
||||
*
|
||||
* // Save to file or upload
|
||||
* await fetch('/api/upload', {
|
||||
* method: 'POST',
|
||||
* body: tarBuffer,
|
||||
* headers: { 'Content-Type': 'application/x-tar' }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
declare function packTar(entries: (TarEntry | ParsedTarEntryWithData)[]): Promise<Uint8Array>;
|
||||
/**
|
||||
* Extracts all entries and their data from a complete tar archive buffer.
|
||||
*
|
||||
* For streaming scenarios or large archives, use {@link createTarDecoder} instead.
|
||||
*
|
||||
* @param archive - The complete tar archive as `ArrayBuffer` or `Uint8Array`
|
||||
* @param options - Optional extraction configuration
|
||||
* @returns A `Promise` that resolves to an array of entries with buffered data
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { unpackTar } from 'modern-tar';
|
||||
*
|
||||
* // From a file upload or fetch
|
||||
* const response = await fetch('/api/archive.tar');
|
||||
* const tarBuffer = await response.arrayBuffer();
|
||||
*
|
||||
* const entries = await unpackTar(tarBuffer);
|
||||
* for (const entry of entries) {
|
||||
* if (entry.data) {
|
||||
* console.log(`File: ${entry.header.name}, Size: ${entry.data.length} bytes`);
|
||||
* const content = new TextDecoder().decode(entry.data);
|
||||
* console.log(`Content: ${content}`);
|
||||
* } else {
|
||||
* console.log(`${entry.header.type}: ${entry.header.name}`);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @example
|
||||
* ```typescript
|
||||
* // From a Uint8Array with options
|
||||
* const tarData = new Uint8Array([...]); // your tar data
|
||||
* const entries = await unpackTar(tarData, {
|
||||
* strip: 1,
|
||||
* filter: (header) => header.name.endsWith('.txt'),
|
||||
* map: (header) => ({ ...header, name: header.name.toLowerCase() })
|
||||
* });
|
||||
*
|
||||
* // Process filtered files
|
||||
* for (const file of entries) {
|
||||
* if (file.data) {
|
||||
* console.log(new TextDecoder().decode(file.data));
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
declare function unpackTar(archive: ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>, options?: UnpackOptions): Promise<ParsedTarEntryWithData[]>;
|
||||
//#endregion
|
||||
//#region src/web/pack.d.ts
|
||||
/**
|
||||
* Controls a streaming tar packing process.
|
||||
*
|
||||
* Provides methods to add entries to a tar archive and finalize the stream.
|
||||
* This is the advanced API for streaming tar creation, allowing you to dynamically
|
||||
* add entries and write their content as a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
|
||||
*/
|
||||
interface TarPackController {
|
||||
/**
|
||||
* Add an entry to the tar archive.
|
||||
*
|
||||
* After adding the entry, you must write exactly `header.size` bytes of data
|
||||
* to the returned [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)
|
||||
* and then close it. For entries that do not have a body (e.g., directories),
|
||||
* the size property should be set to 0 and the stream should be closed immediately.
|
||||
*
|
||||
* @param header - The tar header for the entry. The `size` property must be accurate
|
||||
* @returns A [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) for writing the entry's body data
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Add a text file
|
||||
* const fileStream = controller.add({
|
||||
* name: "file.txt",
|
||||
* size: 11,
|
||||
* type: "file"
|
||||
* });
|
||||
*
|
||||
* const writer = fileStream.getWriter();
|
||||
* await writer.write(new TextEncoder().encode("hello world"));
|
||||
* await writer.close();
|
||||
*
|
||||
* // Add a directory
|
||||
* const dirStream = controller.add({
|
||||
* name: "folder/",
|
||||
* type: "directory",
|
||||
* size: 0
|
||||
* });
|
||||
* await dirStream.close(); // Directories have no content
|
||||
* ```
|
||||
*/
|
||||
add(header: TarHeader): WritableStream<Uint8Array>;
|
||||
/**
|
||||
* Finalize the archive.
|
||||
*
|
||||
* Must be called after all entries have been added.
|
||||
* This writes the end-of-archive marker and closes the readable stream.
|
||||
*/
|
||||
finalize(): void;
|
||||
/**
|
||||
* Abort the packing process with an error.
|
||||
*
|
||||
* @param err - The error that caused the abort
|
||||
*/
|
||||
error(err: unknown): void;
|
||||
}
|
||||
/**
|
||||
* Create a streaming tar packer.
|
||||
*
|
||||
* Provides a controller-based API for creating tar archives, suitable for scenarios where entries are
|
||||
* generated dynamically. The returned [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
|
||||
* outputs tar archive bytes as entries are added.
|
||||
*
|
||||
* @returns Object containing the readable stream and controller
|
||||
* @returns readable - [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) that outputs the tar archive bytes
|
||||
* @returns controller - {@link TarPackController} for adding entries and finalizing
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { createTarPacker } from 'modern-tar';
|
||||
*
|
||||
* const { readable, controller } = createTarPacker();
|
||||
*
|
||||
* // Add entries dynamically
|
||||
* const fileStream = controller.add({
|
||||
* name: "dynamic.txt",
|
||||
* size: 5,
|
||||
* type: "file"
|
||||
* });
|
||||
*
|
||||
* const writer = fileStream.getWriter();
|
||||
* await writer.write(new TextEncoder().encode("hello"));
|
||||
* await writer.close();
|
||||
*
|
||||
* // Add multiple entries
|
||||
* const jsonStream = controller.add({
|
||||
* name: "data.json",
|
||||
* size: 13,
|
||||
* type: "file"
|
||||
* });
|
||||
* const jsonWriter = jsonStream.getWriter();
|
||||
* await jsonWriter.write(new TextEncoder().encode('{"test":true}'));
|
||||
* await jsonWriter.close();
|
||||
*
|
||||
* // Finalize the archive
|
||||
* controller.finalize();
|
||||
*
|
||||
* // Use the readable stream
|
||||
* const response = new Response(readable);
|
||||
* const buffer = await response.arrayBuffer();
|
||||
* ```
|
||||
*/
|
||||
declare function createTarPacker(): {
|
||||
readable: ReadableStream<Uint8Array>;
|
||||
controller: TarPackController;
|
||||
};
|
||||
//#endregion
|
||||
//#region src/web/unpack.d.ts
|
||||
/**
|
||||
* Create a transform stream that parses tar bytes into entries.
|
||||
*
|
||||
* @param options - Optional configuration for the decoder using {@link DecoderOptions}.
|
||||
* @returns `TransformStream` that converts tar archive bytes to {@link ParsedTarEntry} objects.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { createTarDecoder } from 'modern-tar';
|
||||
*
|
||||
* const decoder = createTarDecoder({ strict: true });
|
||||
* const entriesStream = tarStream.pipeThrough(decoder);
|
||||
*
|
||||
* for await (const entry of entriesStream) {
|
||||
* console.log(`Entry: ${entry.header.name}`);
|
||||
*
|
||||
* const shouldSkip = entry.header.name.endsWith('.md');
|
||||
* if (shouldSkip) {
|
||||
* // You MUST drain the body with cancel() to proceed to the next entry or read it fully,
|
||||
* // otherwise the stream will stall.
|
||||
* await entry.body.cancel();
|
||||
* continue;
|
||||
* }
|
||||
*
|
||||
* const reader = entry.body.getReader();
|
||||
* while (true) {
|
||||
* const { done, value } = await reader.read();
|
||||
* if (done) break;
|
||||
* processChunk(value);
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
declare function createTarDecoder(options?: DecoderOptions): TransformStream<Uint8Array, ParsedTarEntry>;
|
||||
//#endregion
|
||||
export { type DecoderOptions, type ParsedTarEntry, type ParsedTarEntryWithData, type TarEntry, type TarEntryData, type TarHeader, type TarPackController, type UnpackOptions, createGzipDecoder, createGzipEncoder, createTarDecoder, createTarPacker, packTar, unpackTar };
|
||||
281
node_modules/modern-tar/dist/web/index.js
generated
vendored
Normal file
281
node_modules/modern-tar/dist/web/index.js
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
import { a as normalizeBody, i as isBodyless, n as createTarPacker$1, r as transformHeader, t as createUnpacker } from "../unpacker-CPCEF5CT.js";
|
||||
//#region src/web/compression.ts
|
||||
function createGzipEncoder() {
|
||||
return new CompressionStream("gzip");
|
||||
}
|
||||
function createGzipDecoder() {
|
||||
return new DecompressionStream("gzip");
|
||||
}
|
||||
//#endregion
|
||||
//#region src/web/pack.ts
|
||||
function createTarPacker() {
|
||||
let streamController;
|
||||
let packer;
|
||||
return {
|
||||
readable: new ReadableStream({ start(controller) {
|
||||
streamController = controller;
|
||||
packer = createTarPacker$1(controller.enqueue.bind(controller), controller.error.bind(controller), controller.close.bind(controller));
|
||||
} }),
|
||||
controller: {
|
||||
add(header) {
|
||||
const bodyless = isBodyless(header);
|
||||
packer.add(header);
|
||||
if (bodyless) packer.endEntry();
|
||||
return new WritableStream({
|
||||
write(chunk) {
|
||||
packer.write(chunk);
|
||||
},
|
||||
close() {
|
||||
if (!bodyless) packer.endEntry();
|
||||
},
|
||||
abort(reason) {
|
||||
streamController.error(reason);
|
||||
}
|
||||
});
|
||||
},
|
||||
finalize() {
|
||||
packer.finalize();
|
||||
},
|
||||
error(err) {
|
||||
streamController.error(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
//#region src/web/stream-utils.ts
|
||||
async function streamToBuffer(stream) {
|
||||
const chunks = [];
|
||||
const reader = stream.getReader();
|
||||
let totalLength = 0;
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
totalLength += value.length;
|
||||
}
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
const drain = (stream) => stream.pipeTo(new WritableStream());
|
||||
//#endregion
|
||||
//#region src/web/unpack.ts
|
||||
function createTarDecoder(options = {}) {
|
||||
const unpacker = createUnpacker(options);
|
||||
const strict = options.strict ?? false;
|
||||
let controller = null;
|
||||
let bodyController = null;
|
||||
let pumping = false;
|
||||
let eofReached = false;
|
||||
let sourceEnded = false;
|
||||
let closed = false;
|
||||
const closeBody = () => {
|
||||
try {
|
||||
bodyController?.close();
|
||||
} catch {}
|
||||
bodyController = null;
|
||||
};
|
||||
const fail = (reason) => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
try {
|
||||
bodyController?.error(reason);
|
||||
} catch {}
|
||||
bodyController = null;
|
||||
try {
|
||||
controller.error(reason);
|
||||
} catch {}
|
||||
controller = null;
|
||||
};
|
||||
const finish = () => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
closeBody();
|
||||
try {
|
||||
controller.close();
|
||||
} catch {}
|
||||
controller = null;
|
||||
};
|
||||
const truncateOrFinish = () => {
|
||||
if (strict) throw new Error("Tar archive is truncated.");
|
||||
finish();
|
||||
};
|
||||
const pump = () => {
|
||||
if (pumping || closed || !controller) return;
|
||||
pumping = true;
|
||||
try {
|
||||
while (true) {
|
||||
if (eofReached) {
|
||||
if (sourceEnded) {
|
||||
unpacker.validateEOF();
|
||||
finish();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (unpacker.isEntryActive()) {
|
||||
if (sourceEnded && !unpacker.canFinish()) {
|
||||
truncateOrFinish();
|
||||
break;
|
||||
}
|
||||
if (bodyController) {
|
||||
if ((bodyController.desiredSize ?? 1) <= 0) break;
|
||||
if (unpacker.streamBody((c) => (bodyController.enqueue(c), (bodyController.desiredSize ?? 1) > 0)) === 0 && !unpacker.isBodyComplete()) {
|
||||
if (sourceEnded) truncateOrFinish();
|
||||
break;
|
||||
}
|
||||
} else if (!unpacker.skipEntry()) {
|
||||
if (sourceEnded) truncateOrFinish();
|
||||
break;
|
||||
}
|
||||
if (unpacker.isBodyComplete()) {
|
||||
closeBody();
|
||||
if (!unpacker.skipPadding()) {
|
||||
if (sourceEnded) truncateOrFinish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((controller.desiredSize ?? 0) < 0) break;
|
||||
const header = unpacker.readHeader();
|
||||
if (header === null) {
|
||||
if (sourceEnded) finish();
|
||||
break;
|
||||
}
|
||||
if (header === void 0) {
|
||||
if (sourceEnded) {
|
||||
unpacker.validateEOF();
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
eofReached = true;
|
||||
break;
|
||||
}
|
||||
controller.enqueue({
|
||||
header,
|
||||
body: new ReadableStream({
|
||||
start(c) {
|
||||
if (header.size === 0) c.close();
|
||||
else bodyController = c;
|
||||
},
|
||||
pull: pump,
|
||||
cancel() {
|
||||
bodyController = null;
|
||||
pump();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
fail(error);
|
||||
throw error;
|
||||
} finally {
|
||||
pumping = false;
|
||||
}
|
||||
};
|
||||
return {
|
||||
readable: new ReadableStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
},
|
||||
pull: pump,
|
||||
cancel(reason) {
|
||||
if (reason !== void 0) fail(reason);
|
||||
else finish();
|
||||
}
|
||||
}, { highWaterMark: 2 }),
|
||||
writable: new WritableStream({
|
||||
write(chunk) {
|
||||
try {
|
||||
if (eofReached && strict && chunk.some((byte) => byte !== 0)) throw new Error("Invalid EOF.");
|
||||
unpacker.write(chunk);
|
||||
pump();
|
||||
} catch (error) {
|
||||
fail(error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
close() {
|
||||
try {
|
||||
sourceEnded = true;
|
||||
unpacker.end();
|
||||
pump();
|
||||
} catch (error) {
|
||||
fail(error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
abort(reason) {
|
||||
fail(reason);
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
//#region src/web/helpers.ts
|
||||
async function packTar(entries) {
|
||||
const { readable, controller } = createTarPacker();
|
||||
await (async () => {
|
||||
for (const entry of entries) {
|
||||
const entryStream = controller.add(entry.header);
|
||||
const body = "body" in entry ? entry.body : entry.data;
|
||||
if (!body) {
|
||||
await entryStream.close();
|
||||
continue;
|
||||
}
|
||||
if (body instanceof ReadableStream) await body.pipeTo(entryStream);
|
||||
else if (body instanceof Blob) await body.stream().pipeTo(entryStream);
|
||||
else try {
|
||||
const chunk = await normalizeBody(body);
|
||||
if (chunk.length > 0) {
|
||||
const writer = entryStream.getWriter();
|
||||
await writer.write(chunk);
|
||||
await writer.close();
|
||||
} else await entryStream.close();
|
||||
} catch {
|
||||
throw new TypeError(`Unsupported content type for entry "${entry.header.name}".`);
|
||||
}
|
||||
}
|
||||
})().then(() => controller.finalize()).catch((err) => controller.error(err));
|
||||
return new Uint8Array(await streamToBuffer(readable));
|
||||
}
|
||||
async function unpackTar(archive, options = {}) {
|
||||
const sourceStream = archive instanceof ReadableStream ? archive : new ReadableStream({ start(controller) {
|
||||
controller.enqueue(archive instanceof Uint8Array ? archive : new Uint8Array(archive));
|
||||
controller.close();
|
||||
} });
|
||||
const results = [];
|
||||
const entryStream = sourceStream.pipeThrough(createTarDecoder(options));
|
||||
for await (const entry of entryStream) {
|
||||
let processedHeader;
|
||||
try {
|
||||
processedHeader = transformHeader(entry.header, options);
|
||||
} catch (error) {
|
||||
await entry.body.cancel();
|
||||
throw error;
|
||||
}
|
||||
if (processedHeader === null) {
|
||||
await drain(entry.body);
|
||||
continue;
|
||||
}
|
||||
if (isBodyless(processedHeader)) {
|
||||
await drain(entry.body);
|
||||
results.push({ header: processedHeader });
|
||||
} else results.push({
|
||||
header: processedHeader,
|
||||
data: await streamToBuffer(entry.body)
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
//#endregion
|
||||
export { createGzipDecoder, createGzipEncoder, createTarDecoder, createTarPacker, packTar, unpackTar };
|
||||
Reference in New Issue
Block a user