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"> <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 { storeHighlightJs } from '@skeletonlabs/skeleton';
import { CodeBlock } from '@skeletonlabs/skeleton'; import { CodeBlock } from '@skeletonlabs/skeleton';
import ToolBox from '$lib/components/ToolBox.svelte'; import ToolBox from '$lib/components/ToolBox.svelte';
@ -98,7 +98,7 @@
>. To use the script: >. To use the script:
</p> </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.sh --help
# Paste69 CLI script # Paste69 CLI script
@ -113,38 +113,70 @@
# -p, --password <password> Set a password for the paste. This enables encryption. # -p, --password <password> Set a password for the paste. This enables encryption.
# -x, --burn Burn the paste after it is viewed once. # -x, --burn Burn the paste after it is viewed once.
# -r, --raw Return the raw JSON response. # -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> <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 <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`}></CodeBlock> # 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> <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"> <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> <a class="underline hover:text-gray-300" href="#api-creating-a-paste">Creating a Paste</a>
</h3> </h3>
<p class="mb-4 mt-4"> <p class="mb-4 mt-4">
To create a paste, send a POST request to <code>/api/pastes</code> To create a paste, send a POST request to <code>/api/pastes</code>. Valid parameters are as follows:
{' '}
with the following JSON body:
</p> </p>
<CodeBlock language="json" code={`{ <ul class="list-disc list-inside mb-4">
"contents": "paste contents", <li>
"language": "txt", <code>contents</code> - The contents of the paste.
"encrypt": false, </li>
"password": "", <li>
"burnAfterReading": false, <code>language</code> - The language of the paste. This is used for syntax highlighting. If no language is specified, the
}`}></CodeBlock> 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> <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={`{ <pre class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg"><code class="language-json">{'{'}
"id": "paste id", "id": "paste id",
"url": "https://0x45.st/some-random-id.md", "url": "https://0x45.st/some-random-id.md",
"contents": "paste contents", "contents": "paste contents",
@ -152,7 +184,7 @@
"encrypted": false, "encrypted": false,
"burnAfterReading": false, "burnAfterReading": false,
"created_at": "2021-08-05T07:30:00.000Z", "created_at": "2021-08-05T07:30:00.000Z",
}`}></CodeBlock> {'}'}</code></pre>
<h3 id="api-fetching-a-paste" class="h3 mt-4 mb-2"> <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> <a class="underline hover:text-gray-300" href="#api-fetching-a-paste">Fetching a Paste</a>
@ -163,7 +195,7 @@
<code>/api/pastes/:id</code>. If the paste exists, the API will respond with the following JSON: <code>/api/pastes/:id</code>. If the paste exists, the API will respond with the following JSON:
</p> </p>
<CodeBlock language="json" code={`{ <pre class="bg-gray-900 py-2 px-4 mb-2 block rounded-lg"><code class="language-json">{'{'}
"id": "paste id", "id": "paste id",
"url": "https://0x45.st/some-random-id.md", "url": "https://0x45.st/some-random-id.md",
"contents": "paste contents", "contents": "paste contents",
@ -171,7 +203,7 @@
"encrypted": false, "encrypted": false,
"burnAfterReading": false, "burnAfterReading": false,
"created_at": "2021-08-05T07:30:00.000Z", "created_at": "2021-08-05T07:30:00.000Z",
}`}></CodeBlock> {'}'}</code></pre>
</div> </div>
<div class="fixed bottom-0 right-0 w-full md:w-auto"> <div class="fixed bottom-0 right-0 w-full md:w-auto">

View File

@ -1,13 +1,111 @@
import { detectLanguage } from "$utils/hljs"; import { detectLanguage } from "$utils/hljs";
import { encrypt as doEncrypt } from "$utils/crypto"; 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 { generate } from 'random-words';
import { Mongo } from "$lib/db/index"; import { Mongo } from "$lib/db/index";
import { env } from "$env/dynamic/private"; import { env } from "$env/dynamic/private";
import { extensionMap } from "$utils/languages"; 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 }) => { 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: '-' }); const id = generate({ exactly: 3, join: '-' });
if (!contents || contents.length === 0) { if (!contents || contents.length === 0) {
@ -28,9 +126,7 @@ export const POST: RequestHandler = async ({ request }) => {
let pasteContents: string = contents; let pasteContents: string = contents;
const highlight = language || detectLanguage(contents) || 'txt'; const highlight = language || detectLanguage(contents) || 'txt';
if (encrypt && !password) { if (password) {
throw error(400, 'No password provided for encryption.');
} else if (encrypt) {
pasteContents = await doEncrypt(contents, password); pasteContents = await doEncrypt(contents, password);
} }
@ -38,7 +134,7 @@ export const POST: RequestHandler = async ({ request }) => {
id, id,
url: `${env.SITE_URL}/${id}.${highlight}`, url: `${env.SITE_URL}/${id}.${highlight}`,
highlight, highlight,
encrypted: !!encrypt, encrypted: !!password,
contents: pasteContents, contents: pasteContents,
burnAfterReading: !!burnAfterReading, burnAfterReading: !!burnAfterReading,
createdAt: new Date(), createdAt: new Date(),
@ -51,7 +147,11 @@ export const POST: RequestHandler = async ({ request }) => {
throw error(500, 'Failed to create paste'); throw error(500, 'Failed to create paste');
} }
if (raw) {
return json(data, { return json(data, {
status: 201, status: 201,
}) });
} else {
return text(data.url);
}
}; };

View File

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

View File

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