Skip to content

Commit

Permalink
track internal keys
Browse files Browse the repository at this point in the history
  • Loading branch information
juliusmarminge committed Jan 25, 2024
1 parent f9a5a15 commit 8d46dab
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 39 deletions.
102 changes: 63 additions & 39 deletions src/drivers/uploadthing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,87 +4,111 @@ import { UTApi } from "uploadthing/server";

export interface UploadThingOptions {
apiKey: string;
/**
* Primarily used for testing
* @default "https://uploadthing.com/api"
*/
uploadthignApiUrl?: string;
/**
* Primarily used for testing
* @default "https://utfs.io/f"
*/
uploadthingFileUrl?: string;
}

export default defineDriver<UploadThingOptions>((opts) => {
let client: UTApi;
let utFetch: $Fetch;
let utApiFetch: $Fetch;
let utFsFetch: $Fetch;

const internalToUTKeyMap = new Map<string, string>();
const fromUTKey = (utKey: string) => {
for (const [key, value] of internalToUTKeyMap.entries()) {
if (value === utKey) {
return key;
}
}
};

const getClient = () => {
return (client ??= new UTApi({
apiKey: opts.apiKey,
fetch: ofetch.native,
logLevel: "debug",
}));
};

const getUTFetch = () => {
return (utFetch ??= ofetch.create({
baseURL: opts.uploadthignApiUrl ?? "https://uploadthing.com/api",
// The UTApi doesn't have all methods we need right now, so use raw fetch
const getUTApiFetch = () => {
return (utApiFetch ??= ofetch.create({
method: "POST",
baseURL: "https://uploadthing.com/api",
headers: {
"x-uploadthing-api-key": opts.apiKey,
},
}));
};

const getUTFsFetch = () => {
return (utFsFetch ??= ofetch.create({
baseURL: "https://utfs.io/f",
}));
};

return {
hasItem(key) {
const utkey = internalToUTKeyMap.get(key);
if (!utkey) return false;
// This is the best endpoint we got currently...
return getUTFetch()("/getFileUrl", {
body: { fileKeys: [key] },
}).then((res) => res.ok);
},
getItem(key) {
return ofetch(`/${key}`, {
baseURL: opts.uploadthingFileUrl ?? "https://utfs.io/f",
return getUTApiFetch()("/getFileUrl", {
body: { fileKeys: [utkey] },
}).then((res) => {
return !!res?.data?.length;
});
},
getItemRaw(key) {
return ofetch
.native(`https://utfs.io/f/${key}`)
.then((res) => res.arrayBuffer());
getItem(key) {
const utkey = internalToUTKeyMap.get(key);
if (!utkey) return null;
return getUTFsFetch()(`/${utkey}`).then((r) => r.text());
},
getKeys() {
return getClient()
.listFiles({})
.then((res) => res.map((file) => file.key));
.then((res) => res.map((file) => fromUTKey(file.key) ?? file.key));
},
setItem(key, value, opts) {
return getClient()
.uploadFiles(new Blob([value]), {
metadata: opts.metadata,
.uploadFiles(Object.assign(new Blob([value]), { name: key }), {
metadata: opts?.metadata,
})
.then(() => {});
.then((response) => {
if (response.error) throw response.error;
internalToUTKeyMap.set(key, response.data.key);
});
},
setItems(items, opts) {
return getClient()
.uploadFiles(
items.map((item) => new Blob([item.value])),
{
metadata: opts?.metadata,
}
items.map((item) =>
Object.assign(new Blob([item.value]), { name: item.key })
),
{ metadata: opts?.metadata }
)
.then(() => {});
.then((responses) => {
responses.map((response) => {
if (response.error) throw response.error;
internalToUTKeyMap.set(response.data.name, response.data.key);
});
});
},
removeItem(key, opts) {
const utkey = internalToUTKeyMap.get(key);
if (!utkey) throw new Error(`Unknown key: ${key}`);
return getClient()
.deleteFiles([key])
.then(() => {});
.deleteFiles([utkey])
.then(() => {
internalToUTKeyMap.delete(key);
});
},
async clear() {
const client = getClient();
const keys = await client.listFiles({}).then((r) => r.map((f) => f.key));
return client.deleteFiles(keys).then(() => {});
const utkeys = Array.from(internalToUTKeyMap.values());
return getClient()
.deleteFiles(utkeys)
.then(() => {
internalToUTKeyMap.clear();
});
},

// getMeta(key, opts) {
// // TODO: We don't currently have an endpoint to fetch metadata, but it does exist
// },
Expand Down
81 changes: 81 additions & 0 deletions test/drivers/uploadthing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { afterAll, beforeAll, describe, it } from "vitest";
import driver from "../../src/drivers/uploadthing";
import { testDriver } from "./utils";
import { setupServer } from "msw/node";
import { rest } from "msw";

const store: Record<string, any> = {};

const utapiUrl = "https://uploadthing.com/api";
const utfsUrl = "https://utfs.io/f";

const server = setupServer(
rest.post(`${utapiUrl}/getFileUrl`, async (req, res, ctx) => {
const { fileKeys } = await req.json();
const key = fileKeys[0];
if (!(key in store)) {
return res(ctx.status(401), ctx.json({ error: "Unauthorized" }));
}
return res(
ctx.status(200),
ctx.json({
result: {
[key]: `https://utfs.io/f/${key}`,
},
})
);
}),
rest.get(`${utfsUrl}/:key`, (req, res, ctx) => {
const key = req.params.key as string;
if (!(key in store)) {
return res(ctx.status(404), ctx.json(null));
}
return res(
ctx.status(200),
ctx.set("content-type", "application/octet-stream"),
ctx.body(store[key])
);
}),
rest.post(`${utapiUrl}/uploadFiles`, async (req, res, ctx) => {
console.log("intercepted request");
return res(
ctx.status(200),
ctx.json({
data: [
{
presignedUrls: [`https://my-s3-server.com/:key`],
},
],
})
);
}),
rest.post(`${utapiUrl}/deleteFile`, async (req, res, ctx) => {
console.log("hello????");
const { fileKeys } = await req.json();
for (const key of fileKeys) {
delete store[key];
}
return res(ctx.status(200), ctx.json({ success: true }));
})
);

describe(
"drivers: uploadthing",
() => {
// beforeAll(() => {
// server.listen();
// });
// afterAll(() => {
// server.close();
// });

testDriver({
driver: driver({
apiKey:
"sk_live_4603822c7c4574cc90495ff3b31204adf20311bc953903d8081be7a5176f31aa",
}),
async additionalTests(ctx) {},
});
},
{ timeout: 30e3 }
);

0 comments on commit 8d46dab

Please sign in to comment.