Skip to content

Commit 9f197da

Browse files
committed
dev-demo: add test api for the s3 createWriteStream
AdminForth/1796/image
1 parent c866c70 commit 9f197da

3 files changed

Lines changed: 100 additions & 1 deletion

File tree

dev-demo/api.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { Express, Response } from "express";
2+
import { randomBytes } from "crypto";
23
import { Filters, IAdminForth, IAdminUserExpressRequest } from "adminforth";
34
import * as z from "zod";
45
import TwoFactorsAuthPlugin from "../plugins/adminforth-two-factors-auth/index.js";
5-
import { levelDbAdapter, resourceAdapter, ramAdapter } from './utils.js';
6+
import { levelDbAdapter, resourceAdapter, ramAdapter, s3StorageAdapter } from './utils.js';
7+
let isLifecycleSetupDone = false;
8+
if (!isLifecycleSetupDone) {
9+
s3StorageAdapter.setupLifecycle();
10+
isLifecycleSetupDone = true;
11+
}
612

713
const CURRENT_KV_ADAPTER = resourceAdapter;
814
const CURRENT_COLLECTION = 'dev-demo';
@@ -15,6 +21,7 @@ const DASHBOARD_CAR_SOURCES = [
1521
{ resourceId: 'cars_ch', label: 'ClickHouse' },
1622
] as const;
1723

24+
1825
type DashboardCarRecord = {
1926
model: string;
2027
price: string | number;
@@ -232,4 +239,51 @@ export function initApi(app: Express, admin: IAdminForth) {
232239
}
233240
)
234241
);
242+
app.post(`${admin.config.baseUrl}/api/testS3WriteStream/`,
243+
admin.express.authorize(
244+
async (_req: IAdminUserExpressRequest, res: Response) => {
245+
const sizeMb = Math.min(Math.max(Number(_req.body?.sizeMb) || 12, 1), 100);
246+
const abort = Boolean(_req.body?.abort);
247+
const key = `dev-demo-write-stream-test/${Date.now()}-${randomBytes(4).toString('hex')}.bin`;
248+
console.log(`[testS3WriteStream] start, key="${key}", sizeMb=${sizeMb}, abort=${abort}`);
249+
250+
const startedAt = Date.now();
251+
try {
252+
const writer = await s3StorageAdapter.createWriteStream(key, 'application/octet-stream');
253+
console.log('[testS3WriteStream] writer created, multipart upload started');
254+
255+
// 1MB of random data per write, so >5MB total gives more than one uploaded part
256+
for (let i = 0; i < sizeMb; i++) {
257+
await writer.write(randomBytes(1024 * 1024));
258+
console.log(`[testS3WriteStream] written chunk ${i + 1}/${sizeMb} (1 MB each)`);
259+
}
260+
261+
if (abort) {
262+
await writer.abort();
263+
console.log(`[testS3WriteStream] aborted upload for key="${key}"`);
264+
res.json({ ok: true, aborted: true, key, sizeMb, tookMs: Date.now() - startedAt });
265+
return;
266+
}
267+
268+
await writer.close();
269+
console.log(`[testS3WriteStream] closed, multipart upload completed for key="${key}"`);
270+
271+
const downloadUrl = await s3StorageAdapter.getDownloadUrl(key, 3600);
272+
console.log(`[testS3WriteStream] done in ${Date.now() - startedAt}ms, url=${downloadUrl}`);
273+
274+
res.json({
275+
ok: true,
276+
aborted: false,
277+
key,
278+
sizeMb,
279+
tookMs: Date.now() - startedAt,
280+
downloadUrl,
281+
});
282+
} catch (e) {
283+
console.error('[testS3WriteStream] failed:', e);
284+
res.status(500).json({ ok: false, key, error: `${e}` });
285+
}
286+
}
287+
)
288+
);
235289
}

dev-demo/custom/SystemTest.vue

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,21 @@
5757
</div>
5858
</section>
5959

60+
<section class="rounded-xl border border-slate-200 bg-slate-50 p-4">
61+
<div class="mb-3 text-xs font-semibold uppercase tracking-wider text-slate-500">
62+
S3 Storage
63+
</div>
64+
<div class="flex flex-col gap-3">
65+
<Input class="w-full" v-model="s3SizeMb" placeholder="Size in MB (default 12)" />
66+
<Button class="w-full" @click="testS3WriteStream(false)">
67+
Start multipart upload (createWriteStream)
68+
</Button>
69+
<Button class="w-full" @click="testS3WriteStream(true)">
70+
Start multipart upload and abort it
71+
</Button>
72+
</div>
73+
</section>
74+
6075
<section class="rounded-xl border border-slate-200 bg-slate-50 p-4">
6176
<div class="mb-3 text-xs font-semibold uppercase tracking-wider text-slate-500">
6277
2FA
@@ -104,6 +119,7 @@ const valueStart = ref()
104119
const kvKey = ref('');
105120
const kvValue = ref('');
106121
const kvPrefix = ref('');
122+
const s3SizeMb = ref('12');
107123
const lastApiOutput = ref('No output yet. Click a button to run an action.');
108124
109125
function setLastApiOutput(actionName: string, data: unknown, isError = false) {
@@ -209,6 +225,26 @@ async function deleteTaskFromTheLastJob() {
209225
}
210226
}
211227
228+
async function testS3WriteStream(abort: boolean) {
229+
const actionName = abort ? 'S3 multipart upload (abort)' : 'S3 multipart upload';
230+
try {
231+
setLastApiOutput(actionName, 'Uploading, check server logs for progress...');
232+
const response = await callApi({
233+
path: '/api/testS3WriteStream/',
234+
method: 'POST',
235+
body: {
236+
sizeMb: Number(s3SizeMb.value) || 12,
237+
abort,
238+
},
239+
});
240+
console.log('S3 write stream result:', response);
241+
setLastApiOutput(actionName, response);
242+
} catch (error) {
243+
console.error('S3 write stream error:', error);
244+
setLastApiOutput(actionName, String(error), true);
245+
}
246+
}
247+
212248
async function callHelloWorldApi() {
213249
try {
214250
const response = await callApi({ path: '/api/hello/', method: 'GET' });

dev-demo/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import LevelDBKeyValueAdapter from '../adapters/adminforth-key-value-adapter-lev
22
import RedisKeyValueAdapter from '../adapters/adminforth-key-value-adapter-redis/index.js';
33
import ResourceKeyValueAdapter from '../adapters/adminforth-key-value-adapter-resource/index.js';
44
import RAMKeyValueAdapter from '../adapters/adminforth-key-value-adapter-ram/index.js';
5+
import AdminForthAdapterS3Storage from '../adapters/adminforth-storage-adapter-amazon-s3/index.js';
56

67
export const levelDbAdapter = new LevelDBKeyValueAdapter({
78
dbPath: './testdb',
@@ -17,6 +18,14 @@ export const resourceAdapter = new ResourceKeyValueAdapter({
1718

1819
export const ramAdapter = new RAMKeyValueAdapter();
1920

21+
export const s3StorageAdapter = process.env.AWS_ACCESS_KEY_ID ? new AdminForthAdapterS3Storage({
22+
bucket: process.env.AWS_BUCKET_NAME as string,
23+
region: process.env.AWS_REGION as string,
24+
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
25+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
26+
s3ACL: "public-read"
27+
}) : null;
28+
2029
// Uncomment if you have running Redis instance
2130
// export const redisAdapter = new RedisKeyValueAdapter({
2231
// redisUrl: 'redis://localhost:6379/0',

0 commit comments

Comments
 (0)