api updates

This commit is contained in:
Chris W 2023-10-22 01:03:54 -06:00
parent c8b1bd07d2
commit 18a75fcc24
4 changed files with 183 additions and 49 deletions

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { ChevronDown, ChevronRight, ChevronUp, Copy, DeviceFloppy, Moon, Sun, TextPlus } from 'svelte-tabler';
import { ChevronDown, ChevronRight, ChevronUp, Copy, DeviceFloppy, InfoCircleFilled, Moon, Sun, TextPlus } from 'svelte-tabler';
import { storeHighlightJs } from '@skeletonlabs/skeleton';
import { CodeBlock } from '@skeletonlabs/skeleton';
import ToolBox from '$lib/components/ToolBox.svelte';
@ -98,7 +98,7 @@
>. To use the script:
</p>
<CodeBlock language="bash" code={`curl -O https://0x45.st/paste69.sh && chmod +x paste69.sh
<pre class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg"><code class="language-bash">curl -O https://0x45.st/paste69.sh && chmod +x paste69.sh
./paste69.sh --help
# Paste69 CLI script
@ -113,46 +113,78 @@
# -p, --password <password> Set a password for the paste. This enables encryption.
# -x, --burn Burn the paste after it is viewed once.
# -r, --raw Return the raw JSON response.
# -c, --copy Copy the paste URL to the clipboard.`}></CodeBlock>
# -c, --copy Copy the paste URL to the clipboard.</code></pre>
<p class="mb-4 mt-4">To create a paste with the script, simply pipe the contents of a file to the script:</p>
<CodeBlock language="bash" code={`cat file.md | ./paste69.sh
# https://0x45.st/some-random-id.md`}></CodeBlock>
<code class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg">cat file.md | ./paste69.sh
# https://0x45.st/some-random-id.md</code>
<h2 id="api" class="h2 mt-4 mb-2"><a class="underline hover:text-gray-300" href="#api">API</a></h2>
<p class="mb-4 mt-4">Paste69 has a simple API for creating and fetching pastes. The API is documented below.</p>
<p class="mb-4 mt-4">
Paste69 has a simple API for creating and fetching pastes. The API accepts JSON, form data, and plain text with
query parameters. The API will respond with JSON or plain text, depenant on the state of the `raw`
parameter.
</p>
<aside class="alert variant-filled-tertiary my-6">
<!-- Icon -->
<div><InfoCircleFilled size="42" /></div>
<!-- Message -->
<div class="alert-message">
<h3 class="h3">Note</h3>
<p>
The below examples are offered as curl commands to make things simple, but you can use whatever tool you want to
make requests to the API.
</p>
</div>
</aside>
<h3 id="api-creating-a-paste" class="h3 mt-4 mb-2">
<a class="underline hover:text-gray-300" href="#api-creating-a-paste">Creating a Paste</a>
</h3>
<p class="mb-4 mt-4">
To create a paste, send a POST request to <code>/api/pastes</code>
{' '}
with the following JSON body:
To create a paste, send a POST request to <code>/api/pastes</code>. Valid parameters are as follows:
</p>
<CodeBlock language="json" code={`{
"contents": "paste contents",
"language": "txt",
"encrypt": false,
"password": "",
"burnAfterReading": false,
}`}></CodeBlock>
<ul class="list-disc list-inside mb-4">
<li>
<code>contents</code> - The contents of the paste.
</li>
<li>
<code>language</code> - The language of the paste. This is used for syntax highlighting. If no language is specified, the
API will attempt to detect the language.
</li>
<li>
<code>password</code> - A password to encrypt the paste with. This will enable encryption.
</li>
<li>
<code>burnAfterReading</code> - A boolean value to enable burn after reading. If this is set to true, the paste will be deleted
after it is viewed once.
</li>
</ul>
<h4 class="h4 mt-4 mb-2">Examples</h4>
<code class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg">curl -X POST -H "Content-Type: application/json" -d '{'{'}"contents": "paste contents"{'}'}' https://0x45.st/api/pastes`}></code>
<code class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg">curl -X POST -F "contents=paste contents" https://0x45.st/api/pastes`}></code>
<code class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg">curl -X POST -d "paste contents" https://0x45.st/api/pastes`}></code>
<p class="mb-4 mt-4">If the paste was successfully created, the API will respond with the following JSON:</p>
<CodeBlock language="json" code={`{
"id": "paste id",
<pre class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg"><code class="language-json">{'{'}
"id": "paste id",
"url": "https://0x45.st/some-random-id.md",
"contents": "paste contents",
"highlight": "txt",
"contents": "paste contents",
"highlight": "txt",
"encrypted": false,
"burnAfterReading": false,
"created_at": "2021-08-05T07:30:00.000Z",
}`}></CodeBlock>
"created_at": "2021-08-05T07:30:00.000Z",
{'}'}</code></pre>
<h3 id="api-fetching-a-paste" class="h3 mt-4 mb-2">
<a class="underline hover:text-gray-300" href="#api-fetching-a-paste">Fetching a Paste</a>
@ -163,15 +195,15 @@
<code>/api/pastes/:id</code>. If the paste exists, the API will respond with the following JSON:
</p>
<CodeBlock language="json" code={`{
"id": "paste id",
<pre class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg"><code class="language-json">{'{'}
"id": "paste id",
"url": "https://0x45.st/some-random-id.md",
"contents": "paste contents",
"highlight": "txt",
"contents": "paste contents",
"highlight": "txt",
"encrypted": false,
"burnAfterReading": false,
"created_at": "2021-08-05T07:30:00.000Z",
}`}></CodeBlock>
"created_at": "2021-08-05T07:30:00.000Z",
{'}'}</code></pre>
</div>
<div class="fixed bottom-0 right-0 w-full md:w-auto">

View File

@ -1,13 +1,111 @@
import { detectLanguage } from "$utils/hljs";
import { encrypt as doEncrypt } from "$utils/crypto";
import { error, json, type RequestHandler } from "@sveltejs/kit";
import { error, json, text, type RequestHandler } from "@sveltejs/kit";
import { generate } from 'random-words';
import { Mongo } from "$lib/db/index";
import { env } from "$env/dynamic/private";
import { extensionMap } from "$utils/languages";
interface PasteOptions {
contents?: string;
language?: string;
encrypt?: boolean;
password?: string;
burnAfterReading?: boolean;
raw?: boolean;
}
const isTrue = (value: string | boolean | undefined): boolean => {
if (typeof value === 'boolean') {
return value;
} else if (typeof value === 'string') {
return ['true', '1', 'yes', 'y', 'on'].includes(value.toLowerCase());
} else {
return false;
}
}
// Extract the options from the form data.
const extractOptionsFromForm = async (req: Request): Promise<PasteOptions> => {
const form = await req.formData();
const contents = form.get('contents') as string;
const language = form.get('language') as string;
const password = form.get('password') as string;
const burnAfterReading = form.get('burnAfterReading') as string;
const raw = form.get('raw') as string;
if (!contents || contents.length === 0) {
throw error(400, 'No contents provided for your paste.')
}
return {
contents,
language,
password,
burnAfterReading: isTrue(burnAfterReading),
raw: isTrue(raw),
};
};
// Extract the options from the JSON body.
const extractOptionsFromJSON = async (req: Request): Promise<PasteOptions> => {
const { contents, language, password, burnAfterReading, raw } = await req.json();
console.log(contents, language, password, burnAfterReading, raw);
if (!contents || contents.length === 0) {
throw error(400, 'No contents provided for your paste.')
}
return {
contents,
language,
password,
burnAfterReading: isTrue(burnAfterReading),
raw: isTrue(raw),
};
};
// Extract the options from the query string, and body.
const extractOptionsFromQuery = async (req: Request): Promise<PasteOptions> => {
const url = new URL(req.url);
const query = url.searchParams;
const reader = req.body?.getReader();
const body = await reader?.read();
const contents = body?.value?.toString();
if (!contents) {
throw error(400, 'No contents provided for your paste.')
}
const language = query.get('language') as string;
const password = query.get('password') as string;
const burnAfterReading = query.get('burnAfterReading') as string;
const raw = query.get('raw') as string;
return {
contents,
language,
password,
burnAfterReading: isTrue(burnAfterReading),
raw: isTrue(raw),
};
};
// Extract the options from the request, depending on the content type.
const extractOptions = async (req: Request): Promise<PasteOptions> => {
const contentType = req.headers.get('content-type') || '';
if (contentType.includes('form')) {
return await extractOptionsFromForm(req);
} else if (contentType.includes('json')) {
return await extractOptionsFromJSON(req);
} else {
return await extractOptionsFromQuery(req);
}
};
export const POST: RequestHandler = async ({ request }) => {
const { contents, language, encrypt, password, burnAfterReading } = await request.json();
const { contents, language, password, burnAfterReading, raw } = await extractOptions(request);
const id = generate({ exactly: 3, join: '-' });
if (!contents || contents.length === 0) {
@ -28,9 +126,7 @@ export const POST: RequestHandler = async ({ request }) => {
let pasteContents: string = contents;
const highlight = language || detectLanguage(contents) || 'txt';
if (encrypt && !password) {
throw error(400, 'No password provided for encryption.');
} else if (encrypt) {
if (password) {
pasteContents = await doEncrypt(contents, password);
}
@ -38,7 +134,7 @@ export const POST: RequestHandler = async ({ request }) => {
id,
url: `${env.SITE_URL}/${id}.${highlight}`,
highlight,
encrypted: !!encrypt,
encrypted: !!password,
contents: pasteContents,
burnAfterReading: !!burnAfterReading,
createdAt: new Date(),
@ -51,7 +147,11 @@ export const POST: RequestHandler = async ({ request }) => {
throw error(500, 'Failed to create paste');
}
return json(data, {
status: 201,
})
if (raw) {
return json(data, {
status: 201,
});
} else {
return text(data.url);
}
};

View File

@ -53,6 +53,7 @@ function show_help {
burn=false
encrypt=false
raw=false
# Parse arguments
while [[ $# -gt 0 ]]; do
@ -134,7 +135,8 @@ json=$(jq -n \
--arg password "$password" \
--argjson encrypt $encrypt \
--argjson burn $burn \
'{"contents": $contents, "extension": $extension, "language": $language, "password": $password, "encrypt": $encrypt, "burnAfterReading": $burn}')
--argjson raw $raw \
'{"contents": $contents, "extension": $extension, "language": $language, "password": $password, "encrypt": $encrypt, "burnAfterReading": $burn, "raw": $raw}')
response=$(
curl -s -X POST $url \
@ -148,24 +150,20 @@ if [ $? -ne 0 ]; then
fi
# Check if there is an error
error=$(echo $response | jq -r '.error')
if [ "$error" != "null" ]; then
error=$(echo "$response" | jq -r '.message' 2>/dev/null || true)
if [ -n "$error" ] && [ "$error" != "null" ]; then
echo "Error: $error"
exit 1
fi
# If raw is set, return the raw JSON response
if [ ! -z "$raw" ]; then
paste_url=$response
if [ "$raw" = true ]; then
paste_url=$(echo $response | jq -r '.url')
echo $response
exit 0
else
echo $paste_url
fi
# Get the paste URL from the response
paste_url=$(echo $response | jq -r '.url')
# Print the paste URL
echo $paste_url
# If copy is set, copy the paste URL to the clipboard
if [ ! -z "$copy" ]; then
if [ ! -z "$clipboard" ]; then

View File

@ -16,6 +16,10 @@ const config = {
alias: {
'$db/*': './src/lib/db/*',
'$utils/*': './src/utils/*',
},
csrf: {
checkOrigin: false,
}
}
};